Compare commits

...

10 Commits

Author SHA1 Message Date
Ginger e713c7c9e1 refactor: Rename PduBuilder to PartialPdu 2026-04-10 11:47:01 -04:00
Ginger 5901efef71 refactor: Add function to state_accessor to get create event 2026-04-09 14:53:12 -04:00
Ginger a5cb5d812b refactor: Consolidate hierarchy and summary logic in a new service 2026-04-09 11:54:45 -04:00
Ginger fc93f79a0c refactor: Fix errors in api/client/membership/ 2026-04-07 13:10:34 -04:00
Ginger 8d1e6a167e refactor: Fix (most) errors in api/client/account/ 2026-04-07 12:57:36 -04:00
Ginger 3746f3c80b chore: Clippy fixes 2026-04-07 12:17:35 -04:00
Ginger d495ad2541 refactor: Replace more uses of RoomVersionId with RoomVersionRules 2026-04-07 10:44:05 -04:00
Ginger be1c344787 fix: Resolve errors in recently added services 2026-04-07 09:59:02 -04:00
Jade Ellis d1c7ee8769 refactor: Ruma upstraming, bake a little more 2026-04-07 14:40:10 +01:00
Ginger d6a2760903 refactor: Ruma upstreaming, half-baked edition
Co-authored-by: Jade Ellis <jade@ellis.link>
2026-04-07 12:38:21 +01:00
176 changed files with 4041 additions and 4072 deletions
Generated
+106 -122
View File
@@ -974,6 +974,7 @@ dependencies = [
name = "conduwuit_admin"
version = "0.5.7-alpha.1"
dependencies = [
"assign",
"clap",
"conduwuit_api",
"conduwuit_core",
@@ -996,6 +997,7 @@ dependencies = [
name = "conduwuit_api"
version = "0.5.7-alpha.1"
dependencies = [
"assign",
"async-trait",
"axum",
"axum-client-ip",
@@ -1017,6 +1019,7 @@ dependencies = [
"rand 0.10.0",
"reqwest",
"ruma",
"ruminuwuity",
"serde",
"serde_html_form",
"serde_json",
@@ -1038,6 +1041,7 @@ version = "0.5.7-alpha.1"
dependencies = [
"argon2",
"arrayvec",
"assign",
"axum",
"axum-extra",
"bytes",
@@ -1089,7 +1093,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",
@@ -1128,6 +1132,7 @@ dependencies = [
name = "conduwuit_router"
version = "0.5.7-alpha.1"
dependencies = [
"assign",
"axum",
"axum-client-ip",
"axum-server",
@@ -1163,6 +1168,7 @@ name = "conduwuit_service"
version = "0.5.7-alpha.1"
dependencies = [
"askama",
"assign",
"async-trait",
"base64 0.22.1",
"blurhash",
@@ -1189,6 +1195,7 @@ dependencies = [
"regex",
"reqwest",
"ruma",
"ruminuwuity",
"rustyline-async",
"sd-notify",
"serde",
@@ -1208,6 +1215,7 @@ name = "conduwuit_web"
version = "0.5.7-alpha.1"
dependencies = [
"askama",
"assign",
"async-trait",
"axum",
"axum-extra",
@@ -1288,16 +1296,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"
@@ -1723,16 +1721,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.1.1"
@@ -2889,30 +2877,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",
]
@@ -3238,16 +3216,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"
@@ -4291,6 +4259,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",
]
@@ -4300,7 +4270,7 @@ version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_chacha 0.9.0",
"rand_core 0.9.5",
]
@@ -4315,6 +4285,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"
@@ -4377,7 +4357,7 @@ dependencies = [
"paste",
"profiling",
"rand 0.9.2",
"rand_chacha",
"rand_chacha 0.9.0",
"simd_helpers",
"thiserror 2.0.18",
"v_frame",
@@ -4544,31 +4524,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",
@@ -4579,13 +4555,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",
@@ -4602,12 +4577,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",
@@ -4615,14 +4591,13 @@ dependencies = [
"js_int",
"konst",
"percent-encoding",
"rand 0.10.0",
"rand 0.8.5",
"regex",
"ruma-identifiers-validation",
"ruma-macros",
"serde",
"serde_html_form",
"serde_json",
"smallvec",
"thiserror 2.0.18",
"time",
"tracing",
@@ -4630,37 +4605,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",
@@ -4670,9 +4642,10 @@ dependencies = [
"js_int",
"memchr",
"mime",
"rand 0.10.0",
"rand 0.8.5",
"ruma-common",
"ruma-events",
"ruma-signatures",
"serde",
"serde_json",
"thiserror 2.0.18",
@@ -4681,28 +4654,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",
@@ -4710,13 +4674,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",
@@ -4727,21 +4691,46 @@ 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.0",
"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 = [
"assign",
"ruma",
"serde",
"serde_json",
"wildmatch",
]
[[package]]
name = "rust-librocksdb-sys"
version = "0.42.0+10.10.1"
@@ -5226,9 +5215,9 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "1.0.4"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
dependencies = [
"serde_core",
]
@@ -5476,15 +5465,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"
@@ -5806,13 +5786,26 @@ checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
dependencies = [
"indexmap",
"serde_core",
"serde_spanned 1.0.4",
"serde_spanned 1.1.1",
"toml_datetime 0.7.5+spec-1.1.0",
"toml_parser",
"toml_writer",
"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.0",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
@@ -5833,9 +5826,9 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "1.0.1+spec-1.1.0"
version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9"
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
dependencies = [
"serde_core",
]
@@ -5861,16 +5854,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1"
dependencies = [
"indexmap",
"toml_datetime 1.0.1+spec-1.1.0",
"toml_datetime 1.1.1+spec-1.1.0",
"toml_parser",
"winnow 1.0.0",
]
[[package]]
name = "toml_parser"
version = "1.0.10+spec-1.1.0"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420"
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
dependencies = [
"winnow 1.0.0",
]
@@ -6111,15 +6104,6 @@ name = "typewit"
version = "1.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8c1ae7cc0fdb8b842d65d127cb981574b0d2b249b74d1c7a2986863dc134f71"
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"
+18 -20
View File
@@ -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"
@@ -340,51 +340,45 @@ version = "0.1.88"
[workspace.dependencies.lru-cache]
version = "0.1.2"
[workspace.dependencies.assign]
version = "1.1.1"
# 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 +651,10 @@ default-features = false
package = "conduwuit"
path = "src/main"
[workspace.dependencies.ruminuwuity]
package = "ruminuwuity"
path = "src/ruminuwuity"
###############################################################################
#
# Release profiles
+1
View File
@@ -82,6 +82,7 @@ const-str.workspace = true
futures.workspace = true
lettre.workspace = true
log.workspace = true
assign.workspace = true
ruma.workspace = true
serde_json.workspace = true
serde-saphyr.workspace = true
+15 -22
View File
@@ -9,7 +9,7 @@
};
use conduwuit::{
Err, Result, debug_warn, error, info,
matrix::{Event, pdu::PduBuilder},
matrix::{Event, pdu::PartialPdu},
utils::{self, ReadyExt},
warn,
};
@@ -732,40 +732,33 @@ pub(super) async fn force_demote(&self, user_id: String, room_id: OwnedRoomOrAli
let state_lock = self.services.rooms.state.mutex.lock(&room_id).await;
let room_power_levels: Option<RoomPowerLevelsEventContent> = self
let mut room_power_levels = self
.services
.rooms
.state_accessor
.room_state_get_content(&room_id, &StateEventType::RoomPowerLevels, "")
.await
.ok();
.get_room_power_levels(&room_id)
.await;
let user_can_demote_self = room_power_levels
.as_ref()
.is_some_and(|power_levels_content| {
RoomPowerLevels::from(power_levels_content.clone())
.user_can_change_user_power_level(&user_id, &user_id)
}) || self
.services
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomCreate, "")
.await
.is_ok_and(|event| event.sender() == user_id);
let user_can_demote_self =
room_power_levels.user_can_change_user_power_level(&user_id, &user_id);
if !user_can_demote_self {
return Err!("User is not allowed to modify their own power levels in the room.",);
}
let mut power_levels_content = room_power_levels.unwrap_or_default();
power_levels_content.users.remove(&user_id);
room_power_levels.users.remove(&user_id);
let event_id = self
.services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(String::new(), &power_levels_content),
PartialPdu::state(
String::new(),
room_power_levels
.try_into()
.expect("PLs should be valid for room version"),
),
&user_id,
Some(&room_id),
&state_lock,
@@ -928,9 +921,9 @@ pub(super) async fn redact_event(&self, event_id: OwnedEventId) -> Result {
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
PartialPdu {
redacts: Some(event.event_id().to_owned()),
..PduBuilder::timeline(&RoomRedactionEventContent {
..PartialPdu::timeline(&RoomRedactionEventContent {
redacts: Some(event.event_id().to_owned()),
reason: Some(reason),
})
+2
View File
@@ -89,7 +89,9 @@ lettre.workspace = true
log.workspace = true
rand.workspace = true
reqwest.workspace = true
assign.workspace = true
ruma.workspace = true
ruminuwuity.workspace = true
serde_html_form.workspace = true
serde_json.workspace = true
serde.workspace = true
+2 -5
View File
@@ -1,10 +1,8 @@
use axum::extract::State;
use conduwuit::{Err, Result, info, utils::ReadyExt, warn};
use futures::{FutureExt, StreamExt};
use ruma::{
OwnedRoomAliasId, continuwuity_admin_api::rooms,
events::room::message::RoomMessageEventContent,
};
use ruma::{OwnedRoomAliasId, events::room::message::RoomMessageEventContent};
use ruminuwuity::admin::continuwuity::rooms;
use crate::{Ruma, client::leave_room};
@@ -36,7 +34,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();
+3 -2
View File
@@ -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
+30 -55
View File
@@ -1,15 +1,15 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{
Err, Event, Result, err, info,
pdu::PduBuilder,
Err, Result, err, info,
pdu::PartialPdu,
utils::{ReadyExt, stream::BroadbandExt},
};
use conduwuit_service::Services;
use futures::{FutureExt, StreamExt};
use lettre::{Address, message::Mailbox};
use ruma::{
OwnedRoomId, OwnedUserId, UserId,
OwnedRoomId, UserId,
api::client::{
account::{
ThirdPartyIdRemovalStatus, change_password, check_registration_token_validity,
@@ -18,12 +18,10 @@
},
uiaa::{AuthFlow, AuthType},
},
events::{
StateEventType,
room::{
member::{MembershipState, RoomMemberEventContent},
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
},
assign,
events::room::{
member::{MembershipState, RoomMemberEventContent},
power_levels::RoomPowerLevelsEventContent,
},
};
use service::{mailer::messages, uiaa::Identity};
@@ -87,7 +85,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`
@@ -143,7 +141,7 @@ pub(crate) async fn change_password_route(
.await?
};
let sender_user = OwnedUserId::parse(format!(
let sender_user = UserId::parse(format!(
"@{}:{}",
identity.localpart.expect("localpart should be known"),
services.globals.server_name()
@@ -161,7 +159,7 @@ pub(crate) async fn change_password_route(
.users
.all_device_ids(&sender_user)
.ready_filter(|id| *id != body.sender_device())
.for_each(|id| services.users.remove_device(&sender_user, id))
.for_each(async |id| services.users.remove_device(&sender_user, &id).await)
.await;
// Remove all pushers except the ones associated with this session
@@ -194,7 +192,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`
@@ -215,7 +213,7 @@ pub(crate) async fn request_password_change_token_via_email_route(
};
let user_id =
OwnedUserId::parse(format!("@{localpart}:{}", services.globals.server_name())).unwrap();
UserId::parse(format!("@{localpart}:{}", services.globals.server_name())).unwrap();
let display_name = services.users.displayname(&user_id).await.ok();
let session = services
@@ -251,11 +249,10 @@ pub(crate) async fn whoami_route(
.map_err(|_| {
err!(Request(Forbidden("Application service has not registered this user.")))
})? && body.appservice_info.is_none();
Ok(whoami::v3::Response {
user_id: body.sender_user().to_owned(),
Ok(assign!(whoami::v3::Response::new(body.sender_user().to_owned(), is_guest), {
device_id: body.sender_device.clone(),
is_guest,
})
}))
}
/// # `POST /_matrix/client/r0/account/deactivate`
@@ -310,9 +307,7 @@ pub(crate) async fn deactivate_route(
.await;
}
Ok(deactivate::v3::Response {
id_server_unbind_result: ThirdPartyIdRemovalStatus::Success,
})
Ok(deactivate::v3::Response::new(ThirdPartyIdRemovalStatus::Success))
}
/// # `GET /_matrix/client/v1/register/m.login.registration_token/validity`
@@ -330,7 +325,7 @@ pub(crate) async fn check_registration_token_validity(
.await
.is_some();
Ok(check_registration_token_validity::v1::Response { valid })
Ok(check_registration_token_validity::v1::Response::new(valid))
}
/// Runs through all the deactivation steps:
@@ -372,53 +367,33 @@ pub async fn full_user_deactivate(
// TODO: Rescind all user invites
let mut pdu_queue: Vec<(PduBuilder, &OwnedRoomId)> = Vec::new();
let mut pdu_queue: Vec<(PartialPdu, &OwnedRoomId)> = Vec::new();
for room_id in all_joined_rooms {
let room_power_levels = services
.rooms
.state_accessor
.room_state_get_content::<RoomPowerLevelsEventContent>(
room_id,
&StateEventType::RoomPowerLevels,
"",
)
.await
.ok();
.get_room_power_levels(&room_id)
.await;
let user_can_demote_self =
room_power_levels
.as_ref()
.is_some_and(|power_levels_content| {
RoomPowerLevels::from(power_levels_content.clone())
.user_can_change_user_power_level(user_id, user_id)
}) || services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomCreate, "")
.await
.is_ok_and(|event| event.sender() == user_id);
room_power_levels.user_can_change_user_power_level(user_id, user_id);
if user_can_demote_self {
let mut power_levels_content = room_power_levels.unwrap_or_default();
if user_can_demote_self
&& let Ok(mut power_levels_content) =
RoomPowerLevelsEventContent::try_from(room_power_levels)
{
power_levels_content.users.remove(user_id);
let pl_evt = PduBuilder::state(String::new(), &power_levels_content);
let pl_evt = PartialPdu::state(String::new(), &power_levels_content);
pdu_queue.push((pl_evt, room_id));
}
// Leave the room
pdu_queue.push((
PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
avatar_url: None,
blurhash: None,
membership: MembershipState::Leave,
displayname: None,
join_authorized_via_users_server: None,
reason: None,
is_direct: None,
third_party_invite: None,
redact_events: None,
}),
PartialPdu::state(
user_id.to_string(),
&RoomMemberEventContent::new(MembershipState::Leave),
),
room_id,
));
+15 -18
View File
@@ -20,7 +20,11 @@
},
uiaa::{AuthFlow, AuthType},
},
events::{GlobalAccountDataEventType, room::message::RoomMessageEventContent},
assign,
events::{
GlobalAccountDataEventType, push_rules::PushRulesEvent,
room::message::RoomMessageEventContent,
},
push,
};
use serde_json::value::RawValue;
@@ -209,23 +213,15 @@ pub(crate) async fn register_route(
None,
&user_id,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(ruma::events::push_rules::PushRulesEvent {
content: ruma::events::push_rules::PushRulesEventContent {
global: push::Ruleset::server_default(&user_id),
},
})?,
&serde_json::to_value(PushRulesEvent::new(
push::Ruleset::server_default(&user_id).into(),
))
.expect("should be able to serialize push rules"),
)
.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,12 +239,14 @@ 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, "User account was created");
// If the user registered with an email, associate it with their account.
if let Some(identity) = identity
&& let Some(email) = identity.email
@@ -393,13 +391,12 @@ pub(crate) async fn register_route(
}
}
Ok(register::v3::Response {
Ok(assign!(register::v3::Response::new(user_id), {
access_token: token,
user_id,
device_id: device,
refresh_token: None,
expires_in: None,
})
}))
}
/// Determine which flows and parameters should be presented when
+3 -3
View File
@@ -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 -1
View File
@@ -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;
+1 -1
View File
@@ -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()))
}
+9 -9
View File
@@ -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(
+1 -1
View File
@@ -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))
}
+3 -6
View File
@@ -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`
+9 -15
View File
@@ -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,16 @@ 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 +99,7 @@ pub(crate) async fn update_device_route(
)
.await?;
return Ok(update_device::v3::Response {});
return Ok(update_device::v3::Response::new());
},
}
}
@@ -124,17 +121,14 @@ 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) {
debug!(
"Skipping UIAA for {sender_user} as this is from an appservice and MSC4190 is \
enabled"
);
if appservice.is_some() {
debug!("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
+10 -36
View File
@@ -26,7 +26,7 @@
},
federation,
},
directory::{Filter, PublicRoomJoinRule, PublicRoomsChunk, RoomNetwork, RoomTypeFilter},
directory::{Filter, PublicRoomsChunk, RoomNetwork, RoomTypeFilter},
events::{
StateEventType,
room::{
@@ -34,6 +34,7 @@
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
},
},
room::JoinRuleKind,
uint,
};
@@ -142,7 +143,13 @@ pub(crate) async fn set_room_visibility_route(
return Err!(Request(Forbidden("Guests cannot publish to room directories")));
}
if !user_can_publish_room(&services, sender_user, &body.room_id).await? {
let room_power_levels = services
.rooms
.state_accessor
.get_room_power_levels(&body.room_id)
.await;
if !room_power_levels.user_can_send_state(user_id, StateEventType::RoomHistoryVisibility) {
return Err!(Request(Forbidden("User is not allowed to publish this room")));
}
@@ -339,39 +346,6 @@ pub(crate) async fn get_public_rooms_filtered_helper(
})
}
/// Check whether the user can publish to the room directory via power levels of
/// room history visibility event or room creator
async fn user_can_publish_room(
services: &Services,
user_id: &UserId,
room_id: &RoomId,
) -> Result<bool> {
match services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")
.await
{
| Ok(event) => serde_json::from_str(event.content().get())
.map_err(|_| err!(Database("Invalid event content for m.room.power_levels")))
.map(|content: RoomPowerLevelsEventContent| {
RoomPowerLevels::from(content)
.user_can_send_state(user_id, StateEventType::RoomHistoryVisibility)
}),
| _ => {
match services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomCreate, "")
.await
{
| Ok(event) => Ok(event.sender() == user_id),
| _ => Err!(Request(Forbidden("User is not allowed to publish this room"))),
}
},
}
}
async fn public_rooms_chunk(services: &Services, room_id: OwnedRoomId) -> PublicRoomsChunk {
let name = services.rooms.state_accessor.get_name(&room_id).ok();
@@ -394,7 +368,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(),
+1 -1
View File
@@ -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,
+13 -14
View File
@@ -1,5 +1,5 @@
use axum::extract::State;
use conduwuit::{Err, Result, matrix::pdu::PduBuilder};
use conduwuit::{Err, Result, matrix::pdu::PartialPdu};
use ruma::{
api::client::membership::ban_user,
events::room::member::{MembershipState, RoomMemberEventContent},
@@ -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
}),
PartialPdu::state(body.user_id.to_string(), &content),
sender_user,
Some(&body.room_id),
&state_lock,
+63 -46
View File
@@ -2,18 +2,19 @@
use axum_client_ip::InsecureClientIp;
use conduwuit::{
Err, Result, debug_error, err, info,
matrix::{event::gen_event_id_canonical_json, pdu::PduBuilder},
matrix::{event::gen_event_id_canonical_json, pdu::PartialPdu},
warn,
};
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::{self, v3::InviteUserId},
federation::membership::{RawStrippedState, create_invite},
},
events::room::member::{MembershipState, RoomMemberEventContent},
};
use ruminuwuity::invite_permission_config::FilterLevel;
use service::Services;
use super::banned_room_check;
@@ -51,7 +52,11 @@ pub(crate) async fn invite_user_route(
.await?;
match &body.recipient {
| invite_user::v3::InvitationRecipient::UserId { user_id: recipient_user } => {
| invite_user::v3::InvitationRecipient::UserId(InviteUserId {
user_id: recipient_user,
reason,
..
}) => {
let sender_filter_level = services
.users
.invite_filter_level(recipient_user, sender_user)
@@ -59,7 +64,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
@@ -95,13 +100,13 @@ pub(crate) async fn invite_user_route(
sender_user,
recipient_user,
&body.room_id,
body.reason.clone(),
reason.clone(),
false,
)
.boxed()
.await?;
Ok(invite_user::v3::Response {})
Ok(invite_user::v3::Response::new())
},
| _ => {
Err!(Request(NotFound("User not found.")))
@@ -141,25 +146,32 @@ 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
.timeline
.create_hash_and_sign_event(
PduBuilder::state(recipient_user.to_string(), &content),
PartialPdu::state(recipient_user.to_string(), &content),
sender_user,
Some(room_id),
&state_lock,
)
.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,32 +180,39 @@ 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
// hashes checks
let (event_id, value) = gen_event_id_canonical_json(&response.event, &room_version_id)
.map_err(|e| {
err!(Request(BadJson(warn!("Could not convert event to canonical JSON: {e}"))))
})?;
let (event_id, value) = gen_event_id_canonical_json(
&response.event,
&room_version_id
.rules()
.expect("room version should have defined rules"),
)
.map_err(|e| {
err!(Request(BadJson(warn!("Could not convert event to canonical JSON: {e}"))))
})?;
if pdu.event_id != event_id {
return Err!(Request(BadJson(warn!(
@@ -229,19 +248,17 @@ 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
.timeline
.build_and_append_pdu(
.PartialPduappend_pdu(
PduBuilder::state(recipient_user.to_string(), &content),
sender_user,
Some(room_id),
+55 -66
View File
@@ -7,7 +7,7 @@
matrix::{
StateKey,
event::{gen_event_id, gen_event_id_canonical_json},
pdu::{PduBuilder, PduEvent},
pdu::{PartialPdu, PduEvent},
state_res,
},
result::FlatOk,
@@ -24,10 +24,8 @@
CanonicalJsonObject, CanonicalJsonValue, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId,
RoomVersionId, UserId,
api::{
client::{
error::ErrorKind,
membership::{join_room_by_id, join_room_by_id_or_alias},
},
client::membership::{join_room_by_id, join_room_by_id_or_alias},
error::{ErrorKind, IncompatibleRoomVersionErrorData},
federation::{self},
},
canonical_json::to_canonical_value,
@@ -89,7 +87,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 +165,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,
);
@@ -209,11 +205,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
)
.await?;
let addl_via_servers = services
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned);
let addl_via_servers = services.rooms.state_cache.servers_invite_via(&room_id);
let addl_state_servers = services
.rooms
@@ -226,7 +218,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 +244,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 +275,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
@@ -383,12 +375,15 @@ async fn join_room_by_id_helper_remote(
info!("make_join finished");
let room_version_id = make_join_response.room_version.unwrap_or(RoomVersionId::V1);
let room_version = make_join_response.room_version.unwrap_or(RoomVersionId::V1);
let room_version_rules = room_version
.rules()
.expect("room version should have defined rules");
if !services.server.supported_room_version(&room_version_id) {
if !services.server.supported_room_version(&room_version) {
// How did we get here?
return Err!(BadServerResponse(
"Remote room version {room_version_id} is not supported by conduwuit"
"Remote room version {room_version} is not supported by conduwuit"
));
}
@@ -401,7 +396,7 @@ async fn join_room_by_id_helper_remote(
let join_authorized_via_users_server = {
use RoomVersionId::*;
if !matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6 | V7) {
if !matches!(room_version, V1 | V2 | V3 | V4 | V5 | V6 | V7) {
join_event_stub
.get("content")
.map(|s| {
@@ -423,36 +418,30 @@ 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)
})
.expect("event is valid, we just created it"),
to_canonical_value(join_content).expect("event is valid, we just created it"),
);
// We keep the "event_id" in the pdu only in v1 or
// v2 rooms
match room_version_id {
| RoomVersionId::V1 | RoomVersionId::V2 => {},
| _ => {
join_event_stub.remove("event_id");
},
}
// Remove event id if it exists
join_event_stub.remove("event_id");
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
services
.server_keys
.hash_and_sign_event(&mut join_event_stub, &room_version_id)?;
.hash_and_sign_event(&mut join_event_stub, &room_version_rules)?;
// Generate event id
let event_id = gen_event_id(&join_event_stub, &room_version_id)?;
let event_id = gen_event_id(&join_event_stub, &room_version_rules)?;
// Add event_id back
join_event_stub
@@ -462,15 +451,14 @@ 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,
};
);
let send_join_response = match services
.sending
@@ -494,7 +482,7 @@ async fn join_room_by_id_helper_remote(
);
let (signed_event_id, signed_value) =
gen_event_id_canonical_json(signed_raw, &room_version_id).map_err(|e| {
gen_event_id_canonical_json(signed_raw, &room_version_rules).map_err(|e| {
err!(Request(BadJson(warn!(
"Could not convert event to canonical JSON: {e}"
))))
@@ -569,7 +557,7 @@ async fn join_room_by_id_helper_remote(
.then(|pdu| {
services
.server_keys
.validate_and_add_event_id_no_fetch(pdu, &room_version_id)
.validate_and_add_event_id_no_fetch(pdu, &room_version_rules)
.inspect_err(|e| {
debug_warn!("Could not validate send_join response room_state event: {e:?}");
})
@@ -617,7 +605,7 @@ async fn join_room_by_id_helper_remote(
.then(|pdu| {
services
.server_keys
.validate_and_add_event_id_no_fetch(pdu, &room_version_id)
.validate_and_add_event_id_no_fetch(pdu, &room_version_rules)
})
.ready_filter_map(Result::ok)
.ready_for_each(|(event_id, value)| {
@@ -638,7 +626,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.rules().unwrap(),
&parsed_join_pdu,
None, // TODO: third party invite
|k, s| state_fetch(k.clone(), s.into()),
@@ -759,21 +747,19 @@ 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
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(sender_user.to_string(), &content),
PartialPdu::state(sender_user.to_string(), &content),
sender_user,
Some(room_id),
&state_lock,
@@ -822,16 +808,16 @@ 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(),
},
)
.send_federation_request(remote_server, request)
.await;
trace!("make_join response: {:?}", make_join_response);
@@ -864,7 +850,10 @@ async fn make_join_request(
rules, but is unable to authorise a join for us. Will continue trying."
);
},
| ErrorKind::IncompatibleRoomVersion { room_version } => {
| ErrorKind::IncompatibleRoomVersion(IncompatibleRoomVersionErrorData {
room_version,
..
}) => {
warn!(
"{remote_server} reports the room we are trying to join is \
v{room_version}, which we do not support."
+11 -15
View File
@@ -1,9 +1,6 @@
use axum::extract::State;
use conduwuit::{Err, Result, matrix::pdu::PduBuilder};
use ruma::{
api::client::membership::kick_user,
events::room::member::{MembershipState, RoomMemberEventContent},
};
use conduwuit::{Err, Result, matrix::pdu::PartialPdu};
use ruma::{api::client::membership::kick_user, events::room::member::MembershipState};
use crate::Ruma;
@@ -18,9 +15,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 +38,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
}),
PartialPdu::state(body.user_id.to_string(), &event),
sender_user,
Some(&body.room_id),
&state_lock,
+73 -81
View File
@@ -6,7 +6,7 @@
Err, Result, debug, debug_info, debug_warn, err, info,
matrix::{
event::gen_event_id,
pdu::{PduBuilder, PduEvent},
pdu::{PartialPdu, PduEvent},
},
result::FlatOk,
trace,
@@ -15,8 +15,8 @@
};
use futures::{FutureExt, StreamExt};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedServerName, RoomId,
RoomVersionId, UserId,
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedServerName,
OwnedUserId, RoomId, UserId,
api::{
client::knock::knock_room,
federation::{self},
@@ -73,7 +73,6 @@ pub(crate) async fn knock_room_route(
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await,
);
@@ -113,11 +112,7 @@ pub(crate) async fn knock_room_route(
)
.await?;
let addl_via_servers = services
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned);
let addl_via_servers = services.rooms.state_cache.servers_invite_via(&room_id);
let addl_state_servers = services
.rooms
@@ -130,7 +125,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 +183,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
@@ -339,34 +334,27 @@ async fn knock_room_helper_local(
) -> Result {
debug_info!("We can knock locally");
let room_version_id = services.rooms.state.get_room_version(room_id).await?;
let room_version = services.rooms.state.get_room_version(room_id).await?;
let room_version_rules = room_version
.rules()
.expect("room version should have defined rules");
if matches!(
room_version_id,
RoomVersionId::V1
| RoomVersionId::V2
| RoomVersionId::V3
| RoomVersionId::V4
| RoomVersionId::V5
| RoomVersionId::V6
) {
if !room_version_rules.authorization.knocking {
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
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(sender_user.to_string(), &content),
PartialPdu::state(sender_user.to_string(), &content),
sender_user,
Some(room_id),
&state_lock,
@@ -381,19 +369,18 @@ async fn knock_room_helper_local(
return Err(error);
}
warn!("We couldn't do the knock locally, maybe federation can help to satisfy the knock");
let (make_knock_response, remote_server) =
make_knock_request(services, sender_user, room_id, servers).await?;
info!("make_knock finished");
let room_version_id = make_knock_response.room_version;
let room_version = make_knock_response.room_version;
let room_version_rules = room_version
.rules()
.expect("room version should have defined rules");
if !services.server.supported_room_version(&room_version_id) {
return Err!(BadServerResponse(
"Remote room version {room_version_id} is not supported by conduwuit"
));
if !services.server.supported_room_version(&room_version) {
return Err!(BadServerResponse("Remote room version {room_version} is not supported"));
}
let mut knock_event_stub = serde_json::from_str::<CanonicalJsonObject>(
@@ -424,24 +411,17 @@ 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)
})
.expect("event is valid, we just created it"),
to_canonical_value(content).expect("event is valid, we just created it"),
);
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
services
.server_keys
.hash_and_sign_event(&mut knock_event_stub, &room_version_id)?;
.hash_and_sign_event(&mut knock_event_stub, &room_version_rules)?;
// Generate event id
let event_id = gen_event_id(&knock_event_stub, &room_version_id)?;
let event_id = gen_event_id(&knock_event_stub, &room_version_rules)?;
// Add event_id
knock_event_stub
@@ -451,14 +431,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
@@ -520,11 +500,14 @@ async fn knock_room_helper_remote(
info!("make_knock finished");
let room_version_id = make_knock_response.room_version;
let room_version = make_knock_response.room_version;
let room_version_rules = room_version
.rules()
.expect("room version should have defined rules");
if !services.server.supported_room_version(&room_version_id) {
if !services.server.supported_room_version(&room_version) {
return Err!(BadServerResponse(
"Remote room version {room_version_id} is not supported by conduwuit"
"Remote room version {room_version} is not supported by conduwuit"
));
}
@@ -545,26 +528,26 @@ 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)
})
.expect("event is valid, we just created it"),
to_canonical_value(knock_content).expect("event is valid, we just created it"),
);
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
services
.server_keys
.hash_and_sign_event(&mut knock_event_stub, &room_version_id)?;
.hash_and_sign_event(&mut knock_event_stub, &room_version_rules)?;
// Generate event id
let event_id = gen_event_id(&knock_event_stub, &room_version_id)?;
let event_id = gen_event_id(&knock_event_stub, &room_version_rules)?;
// Add event_id
knock_event_stub
@@ -574,18 +557,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,
};
);
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 +587,17 @@ 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();
@@ -629,7 +622,7 @@ async fn knock_room_helper_remote(
continue;
};
let event_id = gen_event_id(&event, &room_version_id)?;
let event_id = gen_event_id(&event, &room_version_rules)?;
let shortstatekey = services
.rooms
.short
@@ -709,7 +702,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,16 +715,15 @@ 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(),
},
)
.send_federation_request(remote_server, request)
.await;
trace!("make_knock response: {make_knock_response:?}");
+27 -34
View File
@@ -3,13 +3,13 @@
use axum::extract::State;
use conduwuit::{
Err, Pdu, Result, debug_info, debug_warn, err,
matrix::{event::gen_event_id, pdu::PduBuilder},
matrix::{event::gen_event_id, pdu::PartialPdu},
utils::{self, FutureBoolExt, future::ReadyEqExt},
warn,
};
use futures::{FutureExt, StreamExt, pin_mut};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedServerName, RoomId, RoomVersionId, UserId,
CanonicalJsonObject, CanonicalJsonValue, OwnedServerName, RoomId, UserId,
api::{
client::membership::leave_room,
federation::{self},
@@ -42,11 +42,7 @@ pub(crate) async fn leave_room_route(
// Make a user leave all their joined rooms, rescinds knocks, forgets all rooms,
// and ignores errors
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);
let rooms_joined = services.rooms.state_cache.rooms_joined(user_id);
let rooms_invited = services
.rooms
@@ -142,18 +138,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
}),
PartialPdu::state(user_id.to_string(), &content),
user_id,
Some(room_id),
&state_lock,
@@ -226,7 +221,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 +254,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 +283,10 @@ 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;
@@ -329,6 +323,10 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
)));
}
let room_version_rules = room_version_id
.rules()
.expect("room version should have defined rules");
let mut leave_event_stub = serde_json::from_str::<CanonicalJsonObject>(
make_leave_response.event.get(),
)
@@ -366,21 +364,16 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
}
// room v3 and above removed the "event_id" field from remote PDU format
match room_version_id {
| RoomVersionId::V1 | RoomVersionId::V2 => {},
| _ => {
leave_event_stub.remove("event_id");
},
}
leave_event_stub.remove("event_id");
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
services
.server_keys
.hash_and_sign_event(&mut leave_event_stub, &room_version_id)?;
.hash_and_sign_event(&mut leave_event_stub, &room_version_rules)?;
// Generate event id
let event_id = gen_event_id(&leave_event_stub, &room_version_id)?;
let event_id = gen_event_id(&leave_event_stub, &room_version_rules)?;
// Add event_id back
leave_event_stub
@@ -393,14 +386,14 @@ 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?;
+48 -72
View File
@@ -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,20 +43,20 @@ 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
.rooms
.state_accessor
.room_state_full(&body.room_id)
.ready_filter_map(Result::ok)
.ready_filter(|((ty, _), _)| *ty == StateEventType::RoomMember)
.map(at!(1))
.ready_filter_map(|pdu| membership_filter(pdu, membership, not_membership))
.map(Event::into_format)
.collect()
.boxed()
.await,
})
let chunk = services
.rooms
.state_accessor
.room_state_full(&body.room_id)
.ready_filter_map(Result::ok)
.ready_filter(|((ty, _), _)| *ty == StateEventType::RoomMember)
.map(at!(1))
.ready_filter_map(|pdu| membership_filter(pdu, membership, not_membership))
.map(Event::into_format)
.collect()
.boxed()
.await;
Ok(get_member_events::v3::Response::new(chunk))
}
/// # `POST /_matrix/client/r0/rooms/{roomId}/joined_members`
@@ -78,70 +78,46 @@ 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
.rooms
.state_cache
.room_members(&body.room_id)
.map(ToOwned::to_owned)
.broad_then(|user_id| async move {
let (display_name, avatar_url) = join(
services.users.displayname(&user_id).ok(),
services.users.avatar_url(&user_id).ok(),
)
.await;
let joined = services
.rooms
.state_cache
.room_members(&body.room_id)
.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 })
})
.collect()
.await,
})
(user_id, member)
})
.collect()
.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);
}
+8 -9
View File
@@ -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
.rooms
.state_cache
.rooms_joined(body.sender_user())
.map(ToOwned::to_owned)
.collect()
.await,
})
let joined_rooms = services
.rooms
.state_cache
.rooms_joined(body.sender_user())
.collect()
.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
+10 -11
View File
@@ -1,5 +1,5 @@
use axum::extract::State;
use conduwuit::{Err, Result, matrix::pdu::PduBuilder};
use conduwuit::{Err, Result, matrix::pdu::PartialPdu};
use ruma::{
api::client::membership::unban_user,
events::room::member::{MembershipState, RoomMemberEventContent},
@@ -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
}),
PartialPdu::state(body.user_id.to_string(), &current_member_content),
sender_user,
Some(&body.room_id),
&state_lock,
+6 -6
View File
@@ -1,7 +1,7 @@
use axum::extract::State;
use conduwuit::{
Err, Result,
matrix::pdu::PduBuilder,
matrix::pdu::PartialPdu,
utils::{IterStream, future::TryExtExt, stream::TryIgnore},
warn,
};
@@ -326,7 +326,7 @@ pub async fn update_displayname(
.iter()
.try_stream()
.and_then(|room_id: &OwnedRoomId| async move {
let pdu = PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
let pdu = PartialPdu::state(user_id.to_string(), &RoomMemberEventContent {
displayname: displayname.clone(),
membership: MembershipState::Join,
avatar_url: avatar_url.clone(),
@@ -376,7 +376,7 @@ pub async fn update_avatar_url(
.iter()
.try_stream()
.and_then(|room_id: &OwnedRoomId| async move {
let pdu = PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
let pdu = PartialPdu::state(user_id.to_string(), &RoomMemberEventContent {
avatar_url: avatar_url.clone(),
blurhash: blurhash.clone(),
membership: MembershipState::Join,
@@ -399,15 +399,15 @@ pub async fn update_avatar_url(
pub async fn update_all_rooms(
services: &Services,
all_joined_rooms: Vec<(PduBuilder, &OwnedRoomId)>,
all_joined_rooms: Vec<(PartialPdu, &OwnedRoomId)>,
user_id: &UserId,
) {
for (pdu_builder, room_id) in all_joined_rooms {
for (partial_pdu, room_id) in all_joined_rooms {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
if let Err(e) = services
.rooms
.timeline
.build_and_append_pdu(pdu_builder, user_id, Some(room_id), &state_lock)
.build_and_append_pdu(partial_pdu, user_id, Some(room_id), &state_lock)
.await
{
warn!(%user_id, %room_id, "Failed to update/send new profile join membership update in room: {e}");
+3 -3
View File
@@ -1,6 +1,6 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{Err, Result, matrix::pdu::PduBuilder};
use conduwuit::{Err, Result, matrix::pdu::PartialPdu};
use ruma::{
api::client::redact::redact_event, events::room::redaction::RoomRedactionEventContent,
};
@@ -34,9 +34,9 @@ pub(crate) async fn redact_event_route(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
PartialPdu {
redacts: Some(body.event_id.clone()),
..PduBuilder::timeline(&RoomRedactionEventContent {
..PartialPdu::timeline(&RoomRedactionEventContent {
redacts: Some(body.event_id.clone()),
reason: body.reason.clone(),
})
+1 -1
View File
@@ -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},
+8 -9
View File
@@ -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
.rooms
.alias
.local_aliases_for_room(&body.room_id)
.map(ToOwned::to_owned)
.collect()
.await,
})
let aliases = services
.rooms
.alias
.local_aliases_for_room(&body.room_id)
.collect()
.await;
Ok(aliases::v3::Response::new(aliases))
}
+34 -102
View File
@@ -2,8 +2,8 @@
use axum::extract::State;
use conduwuit::{
Err, Result, RoomVersion, debug, debug_info, debug_warn, err, info,
matrix::{StateKey, pdu::PduBuilder},
Err, Result, debug, debug_info, debug_warn, err, info,
matrix::{StateKey, pdu::PartialPdu},
trace, warn,
};
use conduwuit_service::{Services, appservice::RegistrationInfo};
@@ -13,7 +13,6 @@
api::client::room::{self, create_room},
events::{
TimelineEventType,
invite_permission_config::FilterLevel,
room::{
canonical_alias::RoomCanonicalAliasEventContent,
create::RoomCreateEventContent,
@@ -27,8 +26,10 @@
},
},
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 +82,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 +164,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 +198,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())?;
@@ -234,7 +230,7 @@ pub(crate) async fn create_room_route(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
PartialPdu {
event_type: TimelineEventType::RoomCreate,
content: to_raw_value(&create_content)?,
state_key: Some(StateKey::new()),
@@ -257,30 +253,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)
}),
PartialPdu::state(sender_user.to_string(), &join_event),
sender_user,
Some(&room_id),
&state_lock,
@@ -306,7 +295,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?
@@ -341,7 +330,7 @@ pub(crate) async fn create_room_route(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
PartialPdu {
event_type: TimelineEventType::RoomPowerLevels,
content: to_raw_value(&power_levels_content)?,
state_key: Some(StateKey::new()),
@@ -360,7 +349,7 @@ pub(crate) async fn create_room_route(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(String::new(), &RoomCanonicalAliasEventContent {
PartialPdu::state(String::new(), &RoomCanonicalAliasEventContent {
alias: Some(room_alias_id.to_owned()),
alt_aliases: vec![],
}),
@@ -379,7 +368,7 @@ pub(crate) async fn create_room_route(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(
PartialPdu::state(
String::new(),
&RoomJoinRulesEventContent::new(match preset {
| RoomPreset::PublicChat => JoinRule::Public,
@@ -399,7 +388,7 @@ pub(crate) async fn create_room_route(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(
PartialPdu::state(
String::new(),
&RoomHistoryVisibilityEventContent::new(HistoryVisibility::Shared),
),
@@ -415,7 +404,7 @@ pub(crate) async fn create_room_route(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(
PartialPdu::state(
String::new(),
&RoomGuestAccessEventContent::new(match preset {
| RoomPreset::PublicChat => GuestAccess::Forbidden,
@@ -431,7 +420,7 @@ pub(crate) async fn create_room_route(
// 6. Events listed in initial_state
for event in &body.initial_state {
let mut pdu_builder = event.deserialize_as::<PduBuilder>().map_err(|e| {
let mut partial_pdu = event.deserialize_as::<PartialPdu>().map_err(|e| {
err!(Request(InvalidParam(warn!("Invalid initial state event: {e:?}"))))
})?;
@@ -440,17 +429,17 @@ pub(crate) async fn create_room_route(
// client/appservice workaround: if a user sends an initial_state event with a
// state event in there with the content of literally `{}` (not null or empty
// string), let's just skip it over and warn.
if pdu_builder.content.get().eq("{}") {
if partial_pdu.content.get().eq("{}") {
debug_warn!("skipping empty initial state event with content of `{{}}`: {event:?}");
debug_warn!("content: {}", pdu_builder.content.get());
debug_warn!("content: {}", partial_pdu.content.get());
continue;
}
// Implicit state key defaults to ""
pdu_builder.state_key.get_or_insert_with(StateKey::new);
partial_pdu.state_key.get_or_insert_with(StateKey::new);
// Silently skip encryption events if they are not allowed
if pdu_builder.event_type == TimelineEventType::RoomEncryption
if partial_pdu.event_type == TimelineEventType::RoomEncryption
&& !services.config.allow_encryption
{
continue;
@@ -459,7 +448,7 @@ pub(crate) async fn create_room_route(
services
.rooms
.timeline
.build_and_append_pdu(pdu_builder, sender_user, Some(&room_id), &state_lock)
.build_and_append_pdu(partial_pdu, sender_user, Some(&room_id), &state_lock)
.boxed()
.await?;
}
@@ -470,7 +459,7 @@ pub(crate) async fn create_room_route(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(String::new(), &RoomNameEventContent::new(name.clone())),
PartialPdu::state(String::new(), &RoomNameEventContent::new(name.clone())),
sender_user,
Some(&room_id),
&state_lock,
@@ -484,7 +473,7 @@ pub(crate) async fn create_room_route(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(String::new(), &RoomTopicEventContent { topic: topic.clone() }),
PartialPdu::state(String::new(), &RoomTopicEventContent { topic: topic.clone() }),
sender_user,
Some(&room_id),
&state_lock,
@@ -667,60 +656,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",))
}
+2 -5
View File
@@ -6,10 +6,7 @@
mod upgrade;
pub(crate) use self::{
aliases::get_room_aliases_route,
create::create_room_route,
event::get_room_event_route,
initial_sync::room_initial_sync_route,
summary::{get_room_summary, get_room_summary_legacy},
aliases::get_room_aliases_route, create::create_room_route, event::get_room_event_route,
initial_sync::room_initial_sync_route, summary::get_room_summary,
upgrade::upgrade_room_route,
};
+15 -318
View File
@@ -1,48 +1,11 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{
Err, Result, debug, debug_warn, info, trace,
utils::{IterStream, future::TryExtExt},
};
use futures::{
FutureExt, StreamExt, TryFutureExt,
future::{OptionFuture, join3},
stream::FuturesUnordered,
};
use ruma::{
OwnedServerName, RoomId, UserId,
api::{
client::room::get_summary,
federation::space::{SpaceHierarchyParentSummary, get_hierarchy},
},
events::room::member::MembershipState,
space::SpaceRoomJoinRule::{self, *},
};
use service::Services;
use conduwuit::{Err, Result};
use ruma::api::client::room::get_summary;
use service::rooms::summary::Accessibility;
use crate::{Ruma, RumaResponse};
use crate::Ruma;
/// # `GET /_matrix/client/unstable/im.nheko.summary/rooms/{roomIdOrAlias}/summary`
///
/// Returns a short description of the state of a room.
///
/// This is the "wrong" endpoint that some implementations/clients may use
/// according to the MSC. Request and response bodies are the same as
/// `get_room_summary`.
///
/// An implementation of [MSC3266](https://github.com/matrix-org/matrix-spec-proposals/pull/3266)
pub(crate) async fn get_room_summary_legacy(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_summary::msc3266::Request>,
) -> Result<RumaResponse<get_summary::msc3266::Response>> {
get_room_summary(State(services), InsecureClientIp(client), body)
.boxed()
.await
.map(RumaResponse)
}
/// # `GET /_matrix/client/unstable/im.nheko.summary/summary/{roomIdOrAlias}`
/// # `GET /_matrix/client/v1/room_summary/{roomIdOrAlias}`
///
/// Returns a short description of the state of a room.
@@ -50,8 +13,8 @@ pub(crate) async fn get_room_summary_legacy(
pub(crate) async fn get_room_summary(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_summary::msc3266::Request>,
) -> Result<get_summary::msc3266::Response> {
body: Ruma<get_summary::v1::Request>,
) -> Result<get_summary::v1::Response> {
let (room_id, servers) = services
.rooms
.alias
@@ -62,285 +25,19 @@ pub(crate) async fn get_room_summary(
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
}
room_summary_response(&services, &room_id, &servers, body.sender_user.as_deref())
.boxed()
.await
}
async fn room_summary_response(
services: &Services,
room_id: &RoomId,
servers: &[OwnedServerName],
sender_user: Option<&UserId>,
) -> Result<get_summary::msc3266::Response> {
if services
let summary = services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), room_id)
.await
{
match local_room_summary_response(services, room_id, sender_user)
.boxed()
.await
{
| Ok(response) => return Ok(response),
| Err(e) => {
debug_warn!("Failed to get local room summary: {e:?}, falling back to remote");
},
}
}
let room =
remote_room_summary_hierarchy_response(services, room_id, servers, sender_user).await?;
Ok(get_summary::msc3266::Response {
room_id: room_id.to_owned(),
canonical_alias: room.canonical_alias,
avatar_url: room.avatar_url,
guest_can_join: room.guest_can_join,
name: room.name,
num_joined_members: room.num_joined_members,
topic: room.topic,
world_readable: room.world_readable,
join_rule: room.join_rule,
room_type: room.room_type,
room_version: room.room_version,
encryption: room.encryption,
allowed_room_ids: room.allowed_room_ids,
membership: sender_user.is_some().then_some(MembershipState::Leave),
})
}
async fn local_room_summary_response(
services: &Services,
room_id: &RoomId,
sender_user: Option<&UserId>,
) -> Result<get_summary::msc3266::Response> {
trace!(
sender_user = sender_user.map(tracing::field::display),
"Sending local room summary response for {room_id:?}"
);
let (join_rule, world_readable, guest_can_join) = join3(
services.rooms.state_accessor.get_join_rules(room_id),
services.rooms.state_accessor.is_world_readable(room_id),
services.rooms.state_accessor.guest_can_join(room_id),
)
.await;
// Synapse allows server admins to bypass visibility checks.
// That seems neat so we'll copy that behaviour.
if sender_user.is_none() || !services.users.is_admin(sender_user.unwrap()).await {
user_can_see_summary(
services,
room_id,
&join_rule.clone().into(),
guest_can_join,
world_readable,
join_rule.allowed_rooms(),
sender_user,
)
.summary
.get_room_summary_for_user(body.sender_user.as_deref(), &room_id, &servers)
.await?;
}
let canonical_alias = services
.rooms
.state_accessor
.get_canonical_alias(room_id)
.ok();
let name = services.rooms.state_accessor.get_name(room_id).ok();
let topic = services.rooms.state_accessor.get_room_topic(room_id).ok();
let room_type = services.rooms.state_accessor.get_room_type(room_id).ok();
let avatar_url = services
.rooms
.state_accessor
.get_avatar(room_id)
.map(|res| res.into_option().unwrap_or_default().url);
let room_version = services.rooms.state.get_room_version(room_id).ok();
let encryption = services
.rooms
.state_accessor
.get_room_encryption(room_id)
.ok();
let num_joined_members = services
.rooms
.state_cache
.room_joined_count(room_id)
.unwrap_or(0);
let membership: OptionFuture<_> = sender_user
.map(|sender_user| {
services
.rooms
.state_accessor
.get_member(room_id, sender_user)
.map_ok_or(MembershipState::Leave, |content| content.membership)
})
.into();
let (
canonical_alias,
name,
num_joined_members,
topic,
avatar_url,
room_type,
room_version,
encryption,
membership,
) = futures::join!(
canonical_alias,
name,
num_joined_members,
topic,
avatar_url,
room_type,
room_version,
encryption,
membership,
);
Ok(get_summary::msc3266::Response {
room_id: room_id.to_owned(),
canonical_alias,
avatar_url,
guest_can_join,
name,
num_joined_members: num_joined_members.try_into().unwrap_or_default(),
topic,
world_readable,
room_type,
room_version,
encryption,
membership,
allowed_room_ids: join_rule.allowed_rooms().map(Into::into).collect(),
join_rule: join_rule.into(),
})
}
/// used by MSC3266 to fetch a room's info if we do not know about it
async fn remote_room_summary_hierarchy_response(
services: &Services,
room_id: &RoomId,
servers: &[OwnedServerName],
sender_user: Option<&UserId>,
) -> Result<SpaceHierarchyParentSummary> {
trace!(sender_user = ?sender_user.map(tracing::field::display), ?servers, "Sending remote room summary response for {room_id:?}");
if !services.config.allow_federation {
return Err!(Request(Forbidden("Federation is disabled.")));
}
if services.rooms.metadata.is_disabled(room_id).await {
return Err!(Request(Forbidden(
"Federaton of room {room_id} is currently disabled on this server."
)));
}
if servers.is_empty() {
return Err!(Request(MissingParam(
"No servers were provided to fetch the room over federation"
)));
}
let request = get_hierarchy::v1::Request::new(room_id.to_owned());
let mut requests: FuturesUnordered<_> = servers
.iter()
.map(|server| {
info!("Fetching room summary for {room_id} from server {server}");
services
.sending
.send_federation_request(server, request.clone())
.inspect_ok(move |v| {
debug!("Fetched room summary for {room_id} from server {server}: {v:?}");
})
.inspect_err(move |e| {
info!("Failed to fetch room summary for {room_id} from server {server}: {e}");
})
})
.collect();
while let Some(Ok(response)) = requests.next().await {
trace!("{response:?}");
let room = response.room.clone();
if room.room_id != room_id {
debug_warn!(
"Room ID {} returned does not belong to the requested room ID {}",
room.room_id,
room_id
);
continue;
}
if sender_user.is_none() || !services.users.is_admin(sender_user.unwrap()).await {
return user_can_see_summary(
services,
room_id,
&room.join_rule,
room.guest_can_join,
room.world_readable,
room.allowed_room_ids.iter().map(AsRef::as_ref),
sender_user,
)
.await
.map(|()| room);
}
return Ok(room);
}
Err!(Request(NotFound("Room not found or is not accessible")))
}
async fn user_can_see_summary<'a, I>(
services: &Services,
room_id: &RoomId,
join_rule: &SpaceRoomJoinRule,
guest_can_join: bool,
world_readable: bool,
allowed_room_ids: I,
sender_user: Option<&UserId>,
) -> Result
where
I: Iterator<Item = &'a RoomId> + Send,
{
let is_public_room = matches!(join_rule, Public | Knock | KnockRestricted);
match sender_user {
| Some(sender_user) => {
let user_can_see_state_events = services
.rooms
.state_accessor
.user_can_see_state_events(sender_user, room_id);
let is_guest = services.users.is_deactivated(sender_user).unwrap_or(false);
let user_in_allowed_restricted_room = allowed_room_ids
.stream()
.any(|room| services.rooms.state_cache.is_joined(sender_user, room));
let (user_can_see_state_events, is_guest, user_in_allowed_restricted_room) =
join3(user_can_see_state_events, is_guest, user_in_allowed_restricted_room)
.boxed()
.await;
if user_can_see_state_events
|| (is_guest && guest_can_join)
|| is_public_room
|| user_in_allowed_restricted_room
{
return Ok(());
}
Err!(Request(Forbidden("Room is not accessible")))
match summary {
| Accessibility::Accessible(summary) => Ok(get_summary::v1::Response::new(summary)),
| Accessibility::Inaccessible => {
Err!(Request(Forbidden("You may not preview this room."), FORBIDDEN))
},
| None => {
if is_public_room || world_readable {
return Ok(());
}
Err!(Request(Forbidden("Room is not accessible")))
| Accessibility::NotFound => {
Err!(Request(Forbidden("This room does not exist."), FORBIDDEN))
},
}
}
+10 -10
View File
@@ -3,7 +3,7 @@
use axum::extract::State;
use conduwuit::{
Err, Error, Event, Result, RoomVersion, debug, err, info,
matrix::{StateKey, pdu::PduBuilder},
matrix::{StateKey, pdu::PartialPdu},
};
use futures::{FutureExt, StreamExt};
use ruma::{
@@ -85,7 +85,7 @@ pub(crate) async fn upgrade_room_route(
.rooms
.timeline
.create_hash_and_sign_event(
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
PartialPdu::state(StateKey::new(), &RoomTombstoneEventContent {
body: "This room has been replaced".to_owned(),
replacement_room: RoomId::new(services.globals.server_name()),
}),
@@ -128,7 +128,7 @@ pub(crate) async fn upgrade_room_route(
let tombstone_event_id = services
.rooms
.timeline
.build_and_append_pdu(
.PartialPduappend_pdu(
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
body: "This room has been replaced".to_owned(),
replacement_room: replacement_room.unwrap().to_owned(),
@@ -209,7 +209,7 @@ pub(crate) async fn upgrade_room_route(
let create_event_id = services
.rooms
.timeline
.build_and_append_pdu(
.PartialPduappend_pdu(
PduBuilder {
event_type: TimelineEventType::RoomCreate,
content: to_raw_value(&create_event_content)
@@ -237,7 +237,7 @@ pub(crate) async fn upgrade_room_route(
services
.rooms
.timeline
.build_and_append_pdu(
.PartialPduappend_pdu(
PduBuilder {
event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
@@ -304,7 +304,7 @@ pub(crate) async fn upgrade_room_route(
services
.rooms
.timeline
.build_and_append_pdu(
.PartialPduappend_pdu(
PduBuilder {
event_type: event_type.to_string().into(),
content: event_content,
@@ -364,7 +364,7 @@ pub(crate) async fn upgrade_room_route(
services
.rooms
.timeline
.build_and_append_pdu(
.PartialPduappend_pdu(
PduBuilder::state(StateKey::new(), &RoomPowerLevelsEventContent {
events_default: new_level,
invite: new_level,
@@ -387,7 +387,7 @@ pub(crate) async fn upgrade_room_route(
services
.rooms
.timeline
.build_and_append_pdu(
.PartialPduappend_pdu(
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
body: "This room has been replaced".to_owned(),
replacement_room: replacement_room.unwrap().to_owned(),
@@ -434,7 +434,7 @@ pub(crate) async fn upgrade_room_route(
services
.rooms
.timeline
.build_and_append_pdu(
.PartialPduappend_pdu(
PduBuilder {
event_type: StateEventType::SpaceChild.into(),
content: to_raw_value(&RedactedSpaceChildEventContent {})
@@ -457,7 +457,7 @@ pub(crate) async fn upgrade_room_route(
services
.rooms
.timeline
.build_and_append_pdu(
.PartialPduappend_pdu(
PduBuilder {
event_type: StateEventType::SpaceChild.into(),
content: to_raw_value(&SpaceChildEventContent {
+2 -2
View File
@@ -2,7 +2,7 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{Err, Result, err, matrix::pdu::PduBuilder, utils};
use conduwuit::{Err, Result, err, matrix::pdu::PartialPdu, utils};
use ruma::{api::client::message::send_message_event, events::MessageLikeEventType};
use serde_json::from_str;
@@ -79,7 +79,7 @@ pub(crate) async fn send_message_event_route(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
PartialPdu {
event_type: body.event_type.clone().into(),
content,
unsigned: Some(unsigned),
+28 -176
View File
@@ -1,26 +1,12 @@
use std::{
collections::{BTreeSet, VecDeque},
str::FromStr,
};
use axum::extract::State;
use conduwuit::{
Err, Result,
utils::{future::TryExtExt, stream::IterStream},
};
use conduwuit_service::{
Services,
rooms::spaces::{
PaginationToken, SummaryAccessibility, get_parent_children_via, summary_to_chunk,
},
};
use futures::{StreamExt, TryFutureExt, future::OptionFuture};
use ruma::{
OwnedRoomId, OwnedServerName, RoomId, UInt, UserId, api::client::space::get_hierarchy,
};
use conduwuit::{Err, Result};
use ruma::{UInt, api::client::space::get_hierarchy, assign};
use service::rooms::summary::Accessibility;
use crate::Ruma;
const MAX_MAX_DEPTH: u32 = 10;
/// # `GET /_matrix/client/v1/rooms/{room_id}/hierarchy`
///
/// Paginates over the space tree in a depth-first manner to locate child rooms
@@ -29,167 +15,33 @@ pub(crate) async fn get_hierarchy_route(
State(services): State<crate::State>,
body: Ruma<get_hierarchy::v1::Request>,
) -> Result<get_hierarchy::v1::Response> {
let limit = body
.limit
.unwrap_or_else(|| UInt::from(10_u32))
.min(UInt::from(100_u32));
// We don't do pagination for this route (and therefore ignore `limit`), since
// there's no reasonable way to handle a space hierarchy changing during
// pagination.
let max_depth = body
.max_depth
.unwrap_or_else(|| UInt::from(3_u32))
.min(UInt::from(10_u32));
.map(|max_depth| max_depth.min(UInt::from(MAX_MAX_DEPTH)));
let key = body
.from
.as_ref()
.and_then(|s| PaginationToken::from_str(s).ok());
let hierarchy = services
.rooms
.summary
.get_room_hierarchy_for_user(
body.sender_user(),
body.room_id.clone(),
max_depth,
body.suggested_only,
)
.await?;
// Should prevent unexpected behaviour in (bad) clients
if let Some(ref token) = key {
if token.suggested_only != body.suggested_only || token.max_depth != max_depth {
return Err!(Request(InvalidParam(
"suggested_only and max_depth cannot change on paginated requests"
)));
}
match hierarchy {
| Accessibility::Accessible(rooms) =>
Ok(assign!(get_hierarchy::v1::Response::new(), { rooms: rooms })),
| Accessibility::Inaccessible => {
Err!(Request(Forbidden("You may not preview this room."), FORBIDDEN))
},
| Accessibility::NotFound => {
Err!(Request(Forbidden("This room does not exist."), FORBIDDEN))
},
}
get_client_hierarchy(
&services,
body.sender_user(),
&body.room_id,
limit.try_into().unwrap_or(10),
max_depth.try_into().unwrap_or(usize::MAX),
body.suggested_only,
key.as_ref()
.into_iter()
.flat_map(|t| t.short_room_ids.iter()),
)
.await
}
async fn get_client_hierarchy<'a, ShortRoomIds>(
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
limit: usize,
max_depth: usize,
suggested_only: bool,
short_room_ids: ShortRoomIds,
) -> Result<get_hierarchy::v1::Response>
where
ShortRoomIds: Iterator<Item = &'a u64> + Clone + Send + Sync + 'a,
{
type Via = Vec<OwnedServerName>;
type Entry = (OwnedRoomId, Via);
type Rooms = VecDeque<Entry>;
let mut queue: Rooms = [(
room_id.to_owned(),
room_id
.server_name()
.map(ToOwned::to_owned)
.into_iter()
.collect(),
)]
.into();
let mut rooms = Vec::with_capacity(limit);
let mut parents = BTreeSet::new();
while let Some((current_room, via)) = queue.pop_front() {
let summary = services
.rooms
.spaces
.get_summary_and_children_client(&current_room, suggested_only, sender_user, &via)
.await?;
match (summary, current_room == room_id) {
| (None | Some(SummaryAccessibility::Inaccessible), false) => {
// Just ignore other unavailable rooms
},
| (None, true) => {
return Err!(Request(Forbidden("The requested room was not found")));
},
| (Some(SummaryAccessibility::Inaccessible), true) => {
return Err!(Request(Forbidden("The requested room is inaccessible")));
},
| (Some(SummaryAccessibility::Accessible(summary)), _) => {
let populate = parents.len() >= short_room_ids.clone().count();
let mut children: Vec<Entry> = get_parent_children_via(&summary, suggested_only)
.filter(|(room, _)| !parents.contains(room))
.rev()
.map(|(key, val)| (key, val.collect()))
.collect();
if populate {
rooms.push(summary_to_chunk(summary.clone()));
} else {
children = children
.iter()
.rev()
.stream()
.skip_while(|(room, _)| {
services
.rooms
.short
.get_shortroomid(room)
.map_ok(|short| {
Some(&short) != short_room_ids.clone().nth(parents.len())
})
.unwrap_or_else(|_| false)
})
.map(Clone::clone)
.collect::<Vec<Entry>>()
.await
.into_iter()
.rev()
.collect();
}
if !populate && queue.is_empty() && children.is_empty() {
break;
}
parents.insert(current_room.clone());
if rooms.len() >= limit {
break;
}
if parents.len() > max_depth {
continue;
}
queue.extend(children);
},
}
}
let next_batch: OptionFuture<_> = queue
.pop_front()
.map(|(room, _)| async move {
parents.insert(room);
let next_short_room_ids: Vec<_> = parents
.iter()
.stream()
.filter_map(|room_id| services.rooms.short.get_shortroomid(room_id).ok())
.collect()
.await;
(next_short_room_ids.iter().ne(short_room_ids) && !next_short_room_ids.is_empty())
.then_some(PaginationToken {
short_room_ids: next_short_room_ids,
limit: limit.try_into().ok()?,
max_depth: max_depth.try_into().ok()?,
suggested_only,
})
.as_ref()
.map(PaginationToken::to_string)
})
.into();
Ok(get_hierarchy::v1::Response {
next_batch: next_batch.await.flatten(),
rooms,
})
}
+2 -2
View File
@@ -4,7 +4,7 @@
use axum_client_ip::InsecureClientIp;
use conduwuit::{
Err, Result, err,
matrix::{Event, pdu::PduBuilder},
matrix::{Event, pdu::PartialPdu},
utils::BoolExt,
};
use conduwuit_service::Services;
@@ -203,7 +203,7 @@ async fn send_state_event_for_key_helper(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
PartialPdu {
event_type: event_type.to_string().into(),
content: serde_json::from_str(json.json().get())?,
state_key: Some(state_key.into()),
+1 -5
View File
@@ -184,18 +184,14 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
.ruma_route(&client::get_hierarchy_route)
.ruma_route(&client::get_mutual_rooms_route)
.ruma_route(&client::get_room_summary)
.route(
"/_matrix/client/unstable/im.nheko.summary/rooms/{room_id_or_alias}/summary",
get(client::get_room_summary_legacy)
)
.ruma_route(&client::get_suspended_status)
.ruma_route(&client::put_suspended_status)
.ruma_route(&client::well_known_support)
.ruma_route(&client::well_known_client)
.ruma_route(&client::get_rtc_transports)
.ruma_route(&client::room_initial_sync_route)
.route("/_conduwuit/server_version", get(client::conduwuit_server_version))
.route("/_continuwuity/server_version", get(client::conduwuit_server_version))
.ruma_route(&client::room_initial_sync_route)
.route("/client/server.json", get(client::syncv3_client_server_json))
.ruma_route(&admin::rooms::ban::ban_room)
.ruma_route(&admin::rooms::list::list_rooms);
+4 -3
View File
@@ -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))
}
}
+10 -57
View File
@@ -1,13 +1,7 @@
use axum::extract::State;
use conduwuit::{
Err, Result, info,
utils::stream::{BroadbandExt, IterStream},
};
use conduwuit_service::rooms::spaces::{
Identifier, SummaryAccessibility, get_parent_children_via,
};
use futures::{FutureExt, StreamExt};
use conduwuit::{Err, Result, info};
use ruma::api::federation::space::get_hierarchy;
use service::rooms::summary::Accessibility;
use crate::Ruma;
@@ -19,10 +13,6 @@ pub(crate) async fn get_hierarchy_route(
State(services): State<crate::State>,
body: Ruma<get_hierarchy::v1::Request>,
) -> Result<get_hierarchy::v1::Response> {
if !services.rooms.metadata.exists(&body.room_id).await {
return Err!(Request(NotFound("Room does not exist.")));
}
if !services
.rooms
.state_cache
@@ -36,52 +26,15 @@ pub(crate) async fn get_hierarchy_route(
return Err!(Request(NotFound("This server is not participating in that room.")));
}
let room_id = &body.room_id;
let suggested_only = body.suggested_only;
let ref identifier = Identifier::ServerName(body.origin());
match services
let response = services
.rooms
.spaces
.get_summary_and_children_local(room_id, identifier)
.await?
{
| None => Err!(Request(NotFound("The requested room was not found"))),
.summary
.get_local_room_summary_for_server(body.origin(), &body.room_id, body.suggested_only)
.await;
| Some(SummaryAccessibility::Inaccessible) => {
Err!(Request(NotFound("The requested room is inaccessible")))
},
| Some(SummaryAccessibility::Accessible(room)) => {
let (children, inaccessible_children) =
get_parent_children_via(&room, suggested_only)
.stream()
.broad_filter_map(|(child, _via)| async move {
match services
.rooms
.spaces
.get_summary_and_children_local(&child, identifier)
.await
.ok()?
{
| None => None,
| Some(SummaryAccessibility::Inaccessible) =>
Some((None, Some(child))),
| Some(SummaryAccessibility::Accessible(summary)) =>
Some((Some(summary), None)),
}
})
.unzip()
.map(|(children, inaccessible_children): (Vec<_>, Vec<_>)| {
(
children.into_iter().flatten().map(Into::into).collect(),
inaccessible_children.into_iter().flatten().collect(),
)
})
.await;
Ok(get_hierarchy::v1::Response { room, children, inaccessible_children })
},
if let Accessibility::Accessible(response) = response {
Ok(response)
} else {
Err!(Request(NotFound("This room is not accessible.")))
}
}
+3 -3
View File
@@ -1,7 +1,7 @@
use std::borrow::ToOwned;
use axum::extract::State;
use conduwuit::{Err, Error, Result, debug, debug_info, info, matrix::pdu::PduBuilder, warn};
use conduwuit::{Err, Error, Result, debug, debug_info, info, matrix::pdu::PartialPdu, warn};
use conduwuit_service::Services;
use futures::StreamExt;
use ruma::{
@@ -137,7 +137,7 @@ pub(crate) async fn create_join_event_template_route(
.rooms
.timeline
.create_hash_and_sign_event(
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
PartialPdu::state(body.user_id.to_string(), &RoomMemberEventContent {
join_authorized_via_users_server,
..RoomMemberEventContent::new(MembershipState::Join)
}),
@@ -172,7 +172,7 @@ pub(crate) async fn select_authorising_user(
.state_accessor
.user_can_invite(room_id, user, user_id, state_lock)
})
.boxed()
.boPartialPdu
.next()
.await
.map(ToOwned::to_owned);
+2 -2
View File
@@ -1,6 +1,6 @@
use RoomVersionId::*;
use axum::extract::State;
use conduwuit::{Err, Error, Result, debug_warn, info, matrix::pdu::PduBuilder, warn};
use conduwuit::{Err, Error, Result, debug_warn, info, matrix::pdu::PartialPdu, warn};
use ruma::{
RoomVersionId,
api::{client::error::ErrorKind, federation::knock::create_knock_event_template},
@@ -102,7 +102,7 @@ pub(crate) async fn create_knock_event_template_route(
.rooms
.timeline
.create_hash_and_sign_event(
PduBuilder::state(
PartialPdu::state(
body.user_id.to_string(),
&RoomMemberEventContent::new(MembershipState::Knock),
),
+2 -2
View File
@@ -1,5 +1,5 @@
use axum::extract::State;
use conduwuit::{Err, Result, info, matrix::pdu::PduBuilder};
use conduwuit::{Err, Result, info, matrix::pdu::PartialPdu};
use ruma::{
api::federation::membership::prepare_leave_event,
events::room::member::{MembershipState, RoomMemberEventContent},
@@ -53,7 +53,7 @@ pub(crate) async fn create_leave_event_template_route(
.rooms
.timeline
.create_hash_and_sign_event(
PduBuilder::state(
PartialPdu::state(
body.user_id.to_string(),
&RoomMemberEventContent::new(MembershipState::Leave),
),
+1
View File
@@ -91,6 +91,7 @@ rand_core = { version = "0.6.4", features = ["getrandom"] }
regex.workspace = true
reqwest.workspace = true
ring.workspace = true
assign.workspace = true
ruma.workspace = true
sanitize-filename.workspace = true
serde_json.workspace = true
+5 -5
View File
@@ -46,7 +46,7 @@ macro_rules! err {
(Request(Forbidden($level:ident!($($args:tt)+)))) => {{
let mut buf = String::new();
$crate::error::Error::Request(
$crate::ruma::api::client::error::ErrorKind::forbidden(),
$crate::ruma::api::error::ErrorKind::Forbidden,
$crate::err_log!(buf, $level, $($args)+),
$crate::http::StatusCode::BAD_REQUEST
)
@@ -54,7 +54,7 @@ macro_rules! err {
(Request(Forbidden($($args:tt)+))) => {
$crate::error::Error::Request(
$crate::ruma::api::client::error::ErrorKind::forbidden(),
$crate::ruma::api::error::ErrorKind::Forbidden,
$crate::format_maybe!($($args)+),
$crate::http::StatusCode::BAD_REQUEST
)
@@ -63,7 +63,7 @@ macro_rules! err {
(Request($variant:ident($level:ident!($($args:tt)+)))) => {{
let mut buf = String::new();
$crate::error::Error::Request(
$crate::ruma::api::client::error::ErrorKind::$variant,
$crate::ruma::api::error::ErrorKind::$variant,
$crate::err_log!(buf, $level, $($args)+),
$crate::http::StatusCode::BAD_REQUEST
)
@@ -71,7 +71,7 @@ macro_rules! err {
(Request($variant:ident($($args:tt)+))) => {
$crate::error::Error::Request(
$crate::ruma::api::client::error::ErrorKind::$variant,
$crate::ruma::api::error::ErrorKind::$variant,
$crate::format_maybe!($($args)+),
$crate::http::StatusCode::BAD_REQUEST
)
@@ -79,7 +79,7 @@ macro_rules! err {
(Request($variant:ident($($args:tt)+), $status_code:ident)) => {
$crate::error::Error::Request(
$crate::ruma::api::client::error::ErrorKind::$variant,
$crate::ruma::api::error::ErrorKind::$variant,
$crate::format_maybe!($($args)+),
$crate::http::StatusCode::$status_code,
)
+12 -14
View File
@@ -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,8 +86,8 @@ pub enum Error {
// ruma/conduwuit
#[error("Arithmetic operation failed: {0}")]
Arithmetic(Cow<'static, str>),
#[error("{0}: {1}")]
BadRequest(ruma::api::client::error::ErrorKind, &'static str), //TODO: remove
#[error("{0:?}: {1}")]
BadRequest(ruma::api::error::ErrorKind, &'static str), //TODO: remove
#[error("{0}")]
BadServerResponse(Cow<'static, str>),
#[error(transparent)]
@@ -107,7 +103,7 @@ pub enum Error {
#[error("Feature '{0}' is not available on this server.")]
FeatureDisabled(Cow<'static, str>),
#[error("Remote server {0} responded with: {1}")]
Federation(ruma::OwnedServerName, ruma::api::client::error::Error),
Federation(ruma::OwnedServerName, ruma::api::error::Error),
#[error("{0} in {1}")]
InconsistentRoomState(&'static str, ruma::OwnedRoomId),
#[error(transparent)]
@@ -120,12 +116,14 @@ pub enum Error {
Mxid(#[from] ruma::IdParseError),
#[error("from {0}: {1}")]
Redaction(ruma::OwnedServerName, ruma::canonical_json::RedactionError),
#[error("{0}: {1}")]
Request(ruma::api::client::error::ErrorKind, Cow<'static, str>, http::StatusCode),
#[error("{0:?}: {1}")]
Request(ruma::api::error::ErrorKind, Cow<'static, str>, http::StatusCode),
#[error(transparent)]
Ruma(#[from] ruma::api::client::error::Error),
Ruma(#[from] ruma::api::error::Error),
#[error(transparent)]
Signatures(#[from] ruma::signatures::Error),
SignatureJson(#[from] ruma::signatures::JsonError),
#[error(transparent)]
SignatureVerification(#[from] ruma::signatures::VerificationError),
#[error(transparent)]
StateRes(#[from] crate::state_res::Error),
#[error("uiaa")]
@@ -166,14 +164,14 @@ 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};
pub fn kind(&self) -> ruma::api::error::ErrorKind {
use ruma::api::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,
}
}
+12 -23
View File
@@ -3,10 +3,8 @@
use http_body_util::Full;
use ruma::api::{
OutgoingResponse,
client::{
error::{ErrorBody, ErrorKind},
uiaa::UiaaResponse,
},
client::uiaa::UiaaResponse,
error::{ErrorBody, ErrorKind, StandardErrorBody},
};
use super::Error;
@@ -51,15 +49,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::error::Error::new(error.status_code(), body))
}
}
@@ -76,7 +68,7 @@ pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode {
match kind {
// 429
| LimitExceeded { .. } => StatusCode::TOO_MANY_REQUESTS,
| LimitExceeded(_) => StatusCode::TOO_MANY_REQUESTS,
// 413
| TooLarge => StatusCode::PAYLOAD_TOO_LARGE,
@@ -85,37 +77,34 @@ pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode {
| Unrecognized => StatusCode::METHOD_NOT_ALLOWED,
// 404
| NotFound | NotImplemented | FeatureDisabled | SenderIgnored { .. } =>
StatusCode::NOT_FOUND,
| NotFound => StatusCode::NOT_FOUND,
// 403
| GuestAccessForbidden
| ThreepidAuthFailed
| UserDeactivated
| ThreepidDenied
| InviteBlocked
| WrongRoomKeysVersion { .. }
| WrongRoomKeysVersion(_)
| UserSuspended
| Forbidden { .. } => StatusCode::FORBIDDEN,
| Forbidden => StatusCode::FORBIDDEN,
// 401
| UnknownToken { .. } | MissingToken | Unauthorized | UserLocked =>
StatusCode::UNAUTHORIZED,
| UnknownToken(_) | MissingToken | Unauthorized | UserLocked => StatusCode::UNAUTHORIZED,
// 400
| _ => StatusCode::BAD_REQUEST,
}
}
pub(super) fn ruma_error_message(error: &ruma::api::client::error::Error) -> String {
if let ErrorBody::Standard { message, .. } = &error.body {
pub(super) fn ruma_error_message(error: &ruma::api::error::Error) -> String {
if let ErrorBody::Standard(StandardErrorBody { message, .. }) = &error.body {
return message.clone();
}
format!("{error}")
}
pub(super) fn ruma_error_kind(e: &ruma::api::client::error::Error) -> &ErrorKind {
pub(super) fn ruma_error_kind(e: &ruma::api::error::Error) -> &ErrorKind {
e.error_kind().unwrap_or(&ErrorKind::Unknown)
}
+1 -1
View File
@@ -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};
+3 -3
View File
@@ -11,7 +11,7 @@
use ruma::{
CanonicalJsonObject, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, RoomId,
RoomVersionId, UserId, events::TimelineEventType,
RoomVersionId, UserId, events::TimelineEventType, room_version_rules::RoomVersionRules,
};
use serde::Deserialize;
use serde_json::{Value as JsonValue, value::RawValue as RawJsonValue};
@@ -95,11 +95,11 @@ fn get_content<T>(&self) -> Result<T>
}
#[inline]
fn redacts_id(&self, room_version: &RoomVersionId) -> Option<OwnedEventId>
fn redacts_id(&self, room_version_rules: &RoomVersionRules) -> Option<OwnedEventId>
where
Self: Sized,
{
redact::redacts_id(self, room_version)
redact::redacts_id(self, room_version_rules)
}
#[inline]
+1 -1
View File
@@ -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;
+6 -6
View File
@@ -1,7 +1,7 @@
use ruma::{CanonicalJsonObject, OwnedEventId, RoomVersionId};
use ruma::{CanonicalJsonObject, OwnedEventId, room_version_rules::RoomVersionRules};
use serde_json::value::RawValue as RawJsonValue;
use crate::{Result, err};
use crate::{Err, Result, err};
/// Generates a correct eventId for the incoming pdu.
///
@@ -9,12 +9,12 @@
/// CanonicalJsonValue>`.
pub fn gen_event_id_canonical_json(
pdu: &RawJsonValue,
room_version_id: &RoomVersionId,
room_version_rules: &RoomVersionRules,
) -> Result<(OwnedEventId, CanonicalJsonObject)> {
let value: CanonicalJsonObject = serde_json::from_str(pdu.get())
.map_err(|e| err!(BadServerResponse(warn!("Error parsing incoming event: {e:?}"))))?;
let event_id = gen_event_id(&value, room_version_id)?;
let event_id = gen_event_id(&value, room_version_rules)?;
Ok((event_id, value))
}
@@ -22,9 +22,9 @@ pub fn gen_event_id_canonical_json(
/// Generates a correct eventId for the incoming pdu.
pub fn gen_event_id(
value: &CanonicalJsonObject,
room_version_id: &RoomVersionId,
room_version_rules: &RoomVersionRules,
) -> Result<OwnedEventId> {
let reference_hash = ruma::signatures::reference_hash(value, room_version_id)?;
let reference_hash = ruma::signatures::reference_hash(value, room_version_rules)?;
let event_id: OwnedEventId = format!("${reference_hash}").try_into()?;
Ok(event_id)
+9 -9
View File
@@ -1,6 +1,7 @@
use ruma::{
OwnedEventId, RoomVersionId,
events::{TimelineEventType, room::redaction::RoomRedactionEventContent},
room_version_rules::RoomVersionRules,
};
use serde::Deserialize;
use serde_json::value::{RawValue as RawJsonValue, to_raw_value};
@@ -61,7 +62,7 @@ pub(super) fn is_redacted<E: Event>(event: &E) -> bool {
#[must_use]
pub(super) fn redacts_id<E: Event>(
event: &E,
room_version: &RoomVersionId,
room_version_rules: &RoomVersionRules,
) -> Option<OwnedEventId> {
use RoomVersionId::*;
@@ -69,14 +70,13 @@ pub(super) fn redacts_id<E: Event>(
return None;
}
match *room_version {
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
event.redacts().map(ToOwned::to_owned),
| _ =>
event
.get_content::<RoomRedactionEventContent>()
.ok()?
.redacts,
if room_version_rules.redaction.content_field_redacts {
event.redacts().map(ToOwned::to_owned)
} else {
event
.get_content::<RoomRedactionEventContent>()
.ok()?
.redacts
}
}
+2 -2
View File
@@ -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())
}
}
+3 -2
View File
@@ -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 pdu::{PartialPdu, Pdu, PduCount, PduEvent, PduId, RawPduId, ShortId};
pub use state_key::StateKey;
pub use state_res::{RoomVersion, StateMap, TypeStateKey};
pub use state_res::{StateMap, TypeStateKey};
+2 -2
View File
@@ -1,6 +1,6 @@
mod builder;
mod count;
mod id;
mod partial;
mod raw_id;
mod redact;
#[cfg(test)]
@@ -18,9 +18,9 @@
pub use self::{
Count as PduCount, Id as PduId, Pdu as PduEvent, RawId as RawPduId,
builder::{Builder, Builder as PduBuilder},
count::Count,
id::{ShortId, *},
partial::PartialPdu,
raw_id::*,
};
use super::{Event, StateKey};
@@ -2,16 +2,16 @@
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};
use super::StateKey;
/// Build the start of a PDU in order to add it to the Database.
/// An event and its associated metadata, without an ID, signatures, or hashes.
#[derive(Debug, Deserialize)]
pub struct Builder {
pub struct PartialPdu {
#[serde(rename = "type")]
pub event_type: TimelineEventType,
@@ -30,10 +30,10 @@ pub struct Builder {
type Unsigned = BTreeMap<String, serde_json::Value>;
impl Builder {
impl PartialPdu {
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(),
@@ -58,7 +58,7 @@ pub fn timeline<T>(content: &T) -> Self
}
}
impl Default for Builder {
impl Default for PartialPdu {
fn default() -> Self {
Self {
event_type: "m.room.message".into(),
+6 -3
View File
@@ -1,17 +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())
.map_err(|e| Error::Redaction(self.sender.server_name().to_owned(), e))?;
redact_content_in_place(&mut content, &rules.redaction, self.kind.to_string());
let reason = serde_json::to_value(reason).expect("Failed to preserialize reason");
+3 -3
View File
@@ -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(),
)
})
+76 -107
View File
@@ -11,10 +11,10 @@
join_rules::{JoinRule, RoomJoinRulesEventContent},
member::{MembershipState, ThirdPartyInvite},
power_levels::RoomPowerLevelsEventContent,
third_party_invite::RoomThirdPartyInviteEventContent,
},
int,
serde::{Base64, Raw},
room_version_rules::{RoomIdFormatVersion, RoomVersionRules},
serde::Raw,
};
use serde::{
Deserialize,
@@ -28,7 +28,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 +64,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 +119,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 +155,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 +218,17 @@ 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 +309,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 +336,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,9 +349,9 @@ 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 {
if *incoming_event.event_type() == TimelineEventType::from("m.room.aliases") {
debug!("starting m.room.aliases check");
// If sender's domain doesn't matches state_key, reject
@@ -486,7 +490,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 +501,10 @@ 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 +566,10 @@ 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 +603,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 +628,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 +637,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 +669,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 +710,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 +724,10 @@ 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 +878,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 +895,8 @@ 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 +1141,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 +1150,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 +1240,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 +1396,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 +1465,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 +1515,34 @@ 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::{
StateEventType, TimelineEventType,
room::{
join_rules::{
AllowRule, JoinRule, Restricted, RoomJoinRulesEventContent, RoomMembership,
use ruma::{
events::{
StateEventType, TimelineEventType,
room::{
join_rules::{AllowRule, JoinRule, Restricted, RoomJoinRulesEventContent},
member::{MembershipState, RoomMemberEventContent},
},
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 +1579,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 +1624,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 +1669,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 +1714,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 +1776,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 +1794,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 +1848,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,
+52 -48
View File
@@ -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,22 @@
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,
room_version_rules::{RoomIdFormatVersion, RoomVersionRules, StateResolutionVersion},
};
use serde_json::from_str as from_json_str;
pub(crate) use self::error::Error;
pub use self::event_auth::{auth_check, auth_types_for_event};
use self::power_levels::PowerLevelsContentFields;
pub use self::{
event_auth::{auth_check, auth_types_for_event},
room_version::RoomVersion,
};
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 +74,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 +91,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
@@ -114,19 +107,21 @@ 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 {
let csg = calculate_conflicted_subgraph(&conflicting, event_fetch)
.await
.ok_or_else(|| {
Error::InvalidPdu("Failed to calculate conflicted subgraph".to_owned())
})?;
debug!(count = csg.len(), "conflicted subgraph");
trace!(set = ?csg, "conflicted subgraph");
(csg, HashMap::new())
} else {
(HashSet::new(), unconflicted.clone())
};
let (conflicted_state_subgraph, initial_state) = 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(|| {
Error::InvalidPdu("Failed to calculate conflicted subgraph".to_owned())
})?;
debug!(count = csg.len(), "conflicted subgraph");
trace!(set = ?csg, "conflicted subgraph");
(csg, HashMap::new())
} else {
(HashSet::new(), unconflicted.clone())
};
// `all_conflicted` contains unique items
// synapse says `full_set = {eid for eid in full_conflicted_set if eid in
@@ -166,7 +161,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 +590,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 +660,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 +957,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())
}
}
@@ -988,13 +982,14 @@ mod tests {
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 +999,6 @@ mod tests {
use crate::{
debug,
matrix::{Event, EventTypeExt, Pdu as PduEvent},
state_res::room_version::StateResolutionVersion,
utils::stream::IterStream,
};
@@ -1036,7 +1030,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,
@@ -1436,13 +1430,18 @@ async fn test_event_map_none() {
})
.collect();
let resolved =
match super::resolve(&RoomVersionId::V2, &state_sets, &auth_chain, &fetcher, &exists)
.await
{
| Ok(state) => state,
| Err(e) => panic!("{e}"),
};
let resolved = match super::resolve(
&RoomVersionRules::V2,
&state_sets,
&auth_chain,
&fetcher,
&exists,
)
.await
{
| Ok(state) => state,
| Err(e) => panic!("{e}"),
};
assert_eq!(expected, resolved);
}
@@ -1549,13 +1548,18 @@ 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)
.await
{
| Ok(state) => state,
| Err(e) => panic!("{e}"),
};
let resolved = match super::resolve(
&RoomVersionRules::V6,
&state_sets,
&auth_chain,
&fetcher,
&exists,
)
.await
{
| Ok(state) => state,
| Err(e) => panic!("{e}"),
};
debug!(
resolved = ?resolved
+24 -20
View File
@@ -4,15 +4,13 @@
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,
},
room_version_rules::{AuthorizationRules, RoomVersionRules},
serde::deserialize_v1_powerlevel,
};
use serde::Deserialize;
use serde_json::{Error, from_str as from_json_str};
use super::{Result, RoomVersion};
use super::{Result, serde_backports::*};
use crate::error;
#[derive(Deserialize)]
@@ -48,8 +46,11 @@ 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 +62,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 +102,21 @@ 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 +178,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 +220,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 +250,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)
-169
View File
@@ -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::{fmt, marker::PhantomData};
use ruma::{Int, serde::deserialize_v1_powerlevel};
use serde::{
Deserialize, Deserializer,
de::{MapAccess, Visitor},
};
/// 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())
}
+13 -6
View File
@@ -15,7 +15,9 @@
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 +26,7 @@
use super::auth_types_for_event;
use crate::{
Result, RoomVersion, info,
Result, info,
matrix::{Event, EventTypeExt, Pdu, StateMap, pdu::EventHash},
};
@@ -133,9 +135,14 @@ pub(crate) async fn do_check(
let event_map = &event_map;
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)
.await;
let resolved = super::resolve(
&RoomVersionRules::V6,
state_sets,
&auth_chain_sets,
&fetch,
&exists,
)
.await;
match resolved {
| Ok(state) => state,
@@ -154,7 +161,7 @@ pub(crate) async fn do_check(
fake_event.sender(),
fake_event.state_key(),
fake_event.content(),
&RoomVersion::V6,
&RoomVersionRules::V6,
)
.unwrap();
+44
View File
@@ -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) */
])
}
+1 -3
View File
@@ -25,9 +25,7 @@
version,
version::{name, version},
};
pub use matrix::{
Event, EventTypeExt, Pdu, PduCount, PduEvent, PduId, RoomVersion, pdu, state_res,
};
pub use matrix::{Event, EventTypeExt, Pdu, PduCount, PduEvent, PduId, pdu, state_res};
pub use parking_lot::{Mutex as SyncMutex, RwLock as SyncRwLock};
pub use server::Server;
pub use utils::{implement, result, result::Result};
+6 -6
View File
@@ -1,6 +1,6 @@
use std::{fmt, marker::PhantomData, str::FromStr};
use ruma::{CanonicalJsonError, CanonicalJsonObject, canonical_json::try_from_json_map};
use ruma::{CanonicalJsonError, CanonicalJsonObject, canonical_json::to_canonical_value};
use crate::Result;
@@ -11,12 +11,12 @@
pub fn to_canonical_object<T: serde::Serialize>(
value: T,
) -> Result<CanonicalJsonObject, CanonicalJsonError> {
use CanonicalJsonError::SerDe;
use serde::ser::Error;
use ruma::CanonicalJsonValue;
match serde_json::to_value(value).map_err(SerDe)? {
| serde_json::Value::Object(map) => try_from_json_map(map),
| _ => Err(SerDe(serde_json::Error::custom("Value must be an object"))),
match to_canonical_value(value)? {
| CanonicalJsonValue::Object(map) => Ok(map),
| _ => Err(to_canonical_value(1.0_f32).unwrap_err()), /* Hack to return a
* CanonicalJsonError */
}
}
+73 -111
View File
@@ -1,10 +1,13 @@
#![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 +19,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 +41,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 +67,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 +77,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 +94,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 +131,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 +146,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 +158,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 +171,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 +184,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 +195,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 +209,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 +221,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 +321,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 +337,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 +375,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 +394,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 +413,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 +434,20 @@ 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 +458,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 +471,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 +486,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);
+1
View File
@@ -108,6 +108,7 @@ http-body-util.workspace = true
hyper.workspace = true
hyper-util.workspace = true
log.workspace = true
assign.workspace = true
ruma.workspace = true
rustls.workspace = true
rustls.optional = true
+1 -1
View File
@@ -4,7 +4,7 @@
use conduwuit::Error;
use conduwuit_service::{Services, state, state::Guard};
use http::{StatusCode, Uri};
use ruma::api::client::error::ErrorKind;
use ruma::api::error::ErrorKind;
pub(crate) fn build(services: &Arc<Services>) -> (Router, Guard) {
let router = Router::<state::State>::new();
+32
View File
@@ -0,0 +1,32 @@
[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]
assign.workspace = true
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,56 @@
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,35 @@
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 ban;
pub mod list;
+52
View File
@@ -0,0 +1,52 @@
//! `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::error::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::error::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 } }
}
}
+3
View File
@@ -0,0 +1,3 @@
pub mod continuwuity;
pub mod get_suspended;
pub mod set_suspended;
+54
View File
@@ -0,0 +1,54 @@
//! `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::error::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::error::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 } }
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod user_may_invite;
pub mod user_may_join_room;
@@ -0,0 +1,51 @@
//! `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,51 @@
//! `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() }
}
}
+271
View File
@@ -0,0 +1,271 @@
//! Types for invite filtering ([MSC4155]).
//!
//! MSC4155: https://github.com/matrix-org/matrix-spec-proposals/pull/4155
use ruma::{ServerName, UserId, 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() }
}
}
+4
View File
@@ -0,0 +1,4 @@
pub mod user_may_invite;
pub mod user_may_join_room;
pub mod accept_make_join;
@@ -0,0 +1,64 @@
//! `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,63 @@
//! `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() }
}
}
+7
View File
@@ -0,0 +1,7 @@
//! Ruminuwuity: Continuwuity-specific APIs and structs that depend only on Ruma
pub mod admin;
pub mod draupnir_antispam;
pub mod invite_permission_config;
pub mod meowlnir_antispam;
pub mod policy;
+91
View File
@@ -0,0 +1,91 @@
//! Types for the [`org.matrix.msc4284.policy`] event.
//!
//! [`org.matrix.msc4284.policy`]: https://github.com/matrix-org/matrix-spec-proposals/pull/4284
use ruma::{events::EmptyStateKey, exports::ruma_macros::EventContent};
use serde::{Deserialize, Serialize};
#[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 ruma::events::OriginalStateEvent;
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
use super::RoomPolicyEventContent;
#[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())
);
}
}
+4
View File
@@ -0,0 +1,4 @@
pub mod event;
pub mod policy_check;
pub mod policy_sign;
pub mod report_content;
+59
View File
@@ -0,0 +1,59 @@
//! `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 } }
}
}
+55
View File
@@ -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 } }
}
}
+58
View File
@@ -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() }
}
}
+2
View File
@@ -107,7 +107,9 @@ nonzero_ext.workspace = true
rand.workspace = true
regex.workspace = true
reqwest.workspace = true
assign.workspace = true
ruma.workspace = true
ruminuwuity.workspace = true
rustyline-async.workspace = true
rustyline-async.optional = true
serde_json.workspace = true
+14 -6
View File
@@ -7,10 +7,10 @@
use database::{Deserialized, Handle, Ignore, Json, Map};
use futures::{Stream, StreamExt, TryFutureExt};
use ruma::{
RoomId, UserId,
OwnedRoomId, OwnedUserId, RoomId, UserId,
events::{
AnyGlobalAccountDataEvent, AnyRawAccountDataEvent, AnyRoomAccountDataEvent,
GlobalAccountDataEventType, RoomAccountDataEventType,
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, GlobalAccountDataEventType,
RoomAccountDataEventType,
},
serde::Raw,
};
@@ -18,6 +18,12 @@
use crate::{Dep, globals};
#[derive(Debug)]
pub enum AnyRawAccountDataEvent {
Room(Raw<AnyRoomAccountDataEvent>),
Global(Raw<AnyGlobalAccountDataEvent>),
}
pub struct Service {
services: Services,
db: Data,
@@ -132,7 +138,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 +148,10 @@ 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 {
+25 -44
View File
@@ -1,6 +1,6 @@
use std::collections::BTreeMap;
use conduwuit::{Result, info, pdu::PduBuilder};
use conduwuit::{Result, info, pdu::PartialPdu};
use futures::FutureExt;
use ruma::{
RoomId, RoomVersionId,
@@ -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
}),
PartialPdu::state(String::new(), &create_content),
server_user,
Some(&room_id),
&state_lock,
@@ -75,7 +71,7 @@ pub async fn create_admin_room(services: &Services) -> Result {
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(
PartialPdu::state(
String::from(server_user),
&RoomMemberEventContent::new(MembershipState::Join),
),
@@ -89,14 +85,15 @@ 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()
}),
PartialPdu::state(String::new(), &power_levels_content),
server_user,
Some(&room_id),
&state_lock,
@@ -109,7 +106,7 @@ pub async fn create_admin_room(services: &Services) -> Result {
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(String::new(), &RoomJoinRulesEventContent::new(JoinRule::Invite)),
PartialPdu::state(String::new(), &RoomJoinRulesEventContent::new(JoinRule::Invite)),
server_user,
Some(&room_id),
&state_lock,
@@ -122,7 +119,7 @@ pub async fn create_admin_room(services: &Services) -> Result {
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(
PartialPdu::state(
String::new(),
&RoomHistoryVisibilityEventContent::new(HistoryVisibility::Shared),
),
@@ -138,7 +135,7 @@ pub async fn create_admin_room(services: &Services) -> Result {
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(
PartialPdu::state(
String::new(),
&RoomGuestAccessEventContent::new(GuestAccess::Forbidden),
),
@@ -155,7 +152,7 @@ pub async fn create_admin_room(services: &Services) -> Result {
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(String::new(), &RoomNameEventContent::new(room_name)),
PartialPdu::state(String::new(), &RoomNameEventContent::new(room_name)),
server_user,
Some(&room_id),
&state_lock,
@@ -163,13 +160,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),
}),
PartialPdu::state(String::new(), &RoomTopicEventContent::markdown(room_topic)),
server_user,
Some(&room_id),
&state_lock,
@@ -178,16 +174,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(),
}),
PartialPdu::state(String::new(), &alias_content),
server_user,
Some(&room_id),
&state_lock,
@@ -198,20 +192,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(())
}
+14 -20
View File
@@ -1,7 +1,7 @@
use std::collections::BTreeMap;
use conduwuit::{
Err, Result, debug_info, debug_warn, error, implement, matrix::pdu::PduBuilder, warn,
Err, Result, debug_info, debug_warn, error, implement, matrix::pdu::PartialPdu, warn,
};
use ruma::{
RoomId, UserId,
@@ -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"));
@@ -51,7 +51,7 @@ pub async fn make_user_admin(&self, user_id: &UserId) -> Result {
self.services
.timeline
.build_and_append_pdu(
PduBuilder::state(
PartialPdu::state(
String::from(user_id),
&RoomMemberEventContent::new(MembershipState::Invite),
),
@@ -65,7 +65,7 @@ pub async fn make_user_admin(&self, user_id: &UserId) -> Result {
self.services
.timeline
.build_and_append_pdu(
PduBuilder::state(
PartialPdu::state(
String::from(user_id),
&RoomMemberEventContent::new(MembershipState::Join),
),
@@ -79,7 +79,7 @@ pub async fn make_user_admin(&self, user_id: &UserId) -> Result {
self.services
.timeline
.build_and_append_pdu(
PduBuilder::state(
PartialPdu::state(
user_id.to_string(),
&RoomMemberEventContent::new(MembershipState::Invite),
),
@@ -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
@@ -110,7 +110,7 @@ pub async fn make_user_admin(&self, user_id: &UserId) -> Result {
self.services
.timeline
.build_and_append_pdu(
PduBuilder::state(String::new(), &room_power_levels),
PartialPdu::state(String::new(), &room_power_levels),
server_user,
Some(&room_id),
&state_lock,
@@ -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
}),
PartialPdu::state(user_id.to_string(), &member_content),
self.services.globals.server_user.as_ref(),
Some(&room_id),
&state_lock,
+33 -28
View File
@@ -11,26 +11,27 @@
use async_trait::async_trait;
use conduwuit::{Err, SyncRwLock, utils};
use conduwuit_core::{
Error, Event, Result, Server, debug, err, error, error::default_log, pdu::PduBuilder,
Error, Event, Result, Server, debug, err, error, error::default_log, pdu::PartialPdu,
};
pub use create::create_admin_room;
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::{
MediaSource,
message::{
FileInfo, FileMessageEventContent, MessageType, Relation, RoomMessageEventContent,
},
room::message::{
FileInfo, FileMessageEventContent, MessageType, Relation, RoomMessageEventContent,
},
},
};
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,
@@ -61,7 +62,7 @@ pub struct CommandInput {
pub command: String,
pub reply_id: Option<OwnedEventId>,
pub source: InvocationSource,
pub sender: Option<Box<UserId>>,
pub sender: Option<OwnedUserId>,
}
/// Where a command is being invoked from.
@@ -200,19 +201,18 @@ 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
@@ -317,7 +317,7 @@ pub fn command_with_sender(
command: String,
reply_id: Option<OwnedEventId>,
source: InvocationSource,
sender: Box<UserId>,
sender: OwnedUserId,
) -> Result<()> {
self.channel
.0
@@ -452,13 +452,18 @@ pub async fn get_admin_room(&self) -> Result<OwnedRoomId> {
}
async fn handle_response(&self, content: RoomMessageEventContent) -> Result<()> {
let Some(Relation::Reply { in_reply_to }) = content.relates_to.as_ref() else {
let Some(Relation::Reply(reply)) = content.relates_to.as_ref() else {
return Ok(());
};
let Ok(pdu) = self.services.timeline.get_pdu(&in_reply_to.event_id).await else {
let Ok(pdu) = self
.services
.timeline
.get_pdu(&reply.in_reply_to.event_id)
.await
else {
error!(
event_id = ?in_reply_to.event_id,
event_id = ?reply.in_reply_to.event_id,
"Missing admin command in_reply_to event"
);
return Ok(());
@@ -488,7 +493,7 @@ async fn respond_to_room(
.services
.timeline
.build_and_append_pdu(
PduBuilder::timeline(&self.text_or_file(content).await),
PartialPdu::timeline(&self.text_or_file(content).await),
user_id,
Some(room_id),
&state_lock,
@@ -520,7 +525,7 @@ async fn handle_response_error(
self.services
.timeline
.build_and_append_pdu(
PduBuilder::timeline(&content),
PartialPdu::timeline(&content),
user_id,
Some(room_id),
state_lock,
+10 -2
View File
@@ -2,7 +2,11 @@
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 +41,11 @@ 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,
+3 -3
View File
@@ -72,9 +72,9 @@ 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?;

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