mirror of
https://github.com/element-hq/synapse.git
synced 2026-09-16 10:32:47 +00:00
Merge remote-tracking branch 'origin/develop' into matrix-org-hotfixes
This commit is contained in:
@@ -29,17 +29,9 @@ jobs:
|
||||
with:
|
||||
install-project: "false"
|
||||
|
||||
- name: Import order (isort)
|
||||
- name: Run ruff
|
||||
continue-on-error: true
|
||||
run: poetry run isort .
|
||||
|
||||
- name: Code style (black)
|
||||
continue-on-error: true
|
||||
run: poetry run black .
|
||||
|
||||
- name: Semantic checks (ruff)
|
||||
continue-on-error: true
|
||||
run: poetry run ruff --fix .
|
||||
run: poetry run ruff check --fix .
|
||||
|
||||
- run: cargo clippy --all-features --fix -- -D warnings
|
||||
continue-on-error: true
|
||||
@@ -49,4 +41,4 @@ jobs:
|
||||
|
||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
||||
with:
|
||||
commit_message: "Attempt to fix linting"
|
||||
commit_message: "Attempt to fix linting"
|
||||
|
||||
@@ -131,15 +131,8 @@ jobs:
|
||||
with:
|
||||
install-project: "false"
|
||||
|
||||
- name: Import order (isort)
|
||||
run: poetry run isort --check --diff .
|
||||
|
||||
- name: Code style (black)
|
||||
run: poetry run black --check --diff .
|
||||
|
||||
- name: Semantic checks (ruff)
|
||||
# --quiet suppresses the update check.
|
||||
run: poetry run ruff check --quiet .
|
||||
- name: Check style
|
||||
run: poetry run ruff check --output-format=github .
|
||||
|
||||
lint-mypy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
MSC3861: load the issuer and account management URLs from OIDC discovery.
|
||||
@@ -0,0 +1 @@
|
||||
Improve cross-signing upload when using [MSC3861](https://github.com/matrix-org/matrix-spec-proposals/pull/3861) to use a custom UIA flow stage, with web fallback support.
|
||||
@@ -0,0 +1 @@
|
||||
Replace `isort` and `black with `ruff`.
|
||||
@@ -0,0 +1 @@
|
||||
Sliding Sync: Split up `get_room_membership_for_user_at_to_token`.
|
||||
@@ -0,0 +1 @@
|
||||
Use new database tables for sliding sync.
|
||||
@@ -0,0 +1 @@
|
||||
Pre-populate room data used in experimental [MSC3575](https://github.com/matrix-org/matrix-spec-proposals/pull/3575) Sliding Sync `/sync` endpoint for quick filtering/sorting.
|
||||
@@ -0,0 +1 @@
|
||||
Pre-populate room data used in experimental [MSC3575](https://github.com/matrix-org/matrix-spec-proposals/pull/3575) Sliding Sync `/sync` endpoint for quick filtering/sorting.
|
||||
@@ -0,0 +1 @@
|
||||
Pre-populate room data used in experimental [MSC3575](https://github.com/matrix-org/matrix-spec-proposals/pull/3575) Sliding Sync `/sync` endpoint for quick filtering/sorting.
|
||||
@@ -0,0 +1 @@
|
||||
Pre-populate room data used in experimental [MSC3575](https://github.com/matrix-org/matrix-spec-proposals/pull/3575) Sliding Sync `/sync` endpoint for quick filtering/sorting.
|
||||
@@ -0,0 +1 @@
|
||||
Pre-populate room data used in experimental [MSC3575](https://github.com/matrix-org/matrix-spec-proposals/pull/3575) Sliding Sync `/sync` endpoint for quick filtering/sorting.
|
||||
+1
-3
@@ -8,9 +8,7 @@ errors in code.
|
||||
|
||||
The necessary tools are:
|
||||
|
||||
- [black](https://black.readthedocs.io/en/stable/), a source code formatter;
|
||||
- [isort](https://pycqa.github.io/isort/), which organises each file's imports;
|
||||
- [ruff](https://github.com/charliermarsh/ruff), which can spot common errors; and
|
||||
- [ruff](https://github.com/charliermarsh/ruff), which can spot common errors and enforce a consistent style; and
|
||||
- [mypy](https://mypy.readthedocs.io/en/stable/), a type checker.
|
||||
|
||||
See [the contributing guide](development/contributing_guide.md#run-the-linters) for instructions
|
||||
|
||||
Generated
+20
-106
@@ -105,52 +105,6 @@ files = [
|
||||
tests = ["pytest (>=3.2.1,!=3.3.0)"]
|
||||
typecheck = ["mypy"]
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "24.8.0"
|
||||
description = "The uncompromising code formatter."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"},
|
||||
{file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"},
|
||||
{file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"},
|
||||
{file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"},
|
||||
{file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"},
|
||||
{file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"},
|
||||
{file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"},
|
||||
{file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"},
|
||||
{file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"},
|
||||
{file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"},
|
||||
{file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"},
|
||||
{file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"},
|
||||
{file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"},
|
||||
{file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"},
|
||||
{file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"},
|
||||
{file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"},
|
||||
{file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"},
|
||||
{file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"},
|
||||
{file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"},
|
||||
{file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"},
|
||||
{file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"},
|
||||
{file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.0.0"
|
||||
mypy-extensions = ">=0.4.3"
|
||||
packaging = ">=22.0"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
|
||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "bleach"
|
||||
version = "6.1.0"
|
||||
@@ -832,20 +786,6 @@ tomli = {version = "*", markers = "python_version < \"3.11\""}
|
||||
[package.extras]
|
||||
scripts = ["click (>=6.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "isort"
|
||||
version = "5.13.2"
|
||||
description = "A Python utility / library to sort Python imports."
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
files = [
|
||||
{file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
|
||||
{file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
colors = ["colorama (>=0.4.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "jaeger-client"
|
||||
version = "4.8.0"
|
||||
@@ -1494,17 +1434,6 @@ files = [
|
||||
[package.extras]
|
||||
dev = ["jinja2"]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.11.1"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"},
|
||||
{file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phonenumbers"
|
||||
version = "8.13.44"
|
||||
@@ -1638,21 +1567,6 @@ files = [
|
||||
{file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "3.1.1"
|
||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "platformdirs-3.1.1-py3-none-any.whl", hash = "sha256:e5986afb596e4bb5bde29a79ac9061aa955b94fca2399b7aaac4090860920dd8"},
|
||||
{file = "platformdirs-3.1.1.tar.gz", hash = "sha256:024996549ee88ec1a9aa99ff7f8fc819bb59e2c3477b410d90a16d32d6e707aa"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"]
|
||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "prometheus-client"
|
||||
version = "0.20.0"
|
||||
@@ -2354,29 +2268,29 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.5.5"
|
||||
version = "0.6.2"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.5.5-py3-none-linux_armv6l.whl", hash = "sha256:605d589ec35d1da9213a9d4d7e7a9c761d90bba78fc8790d1c5e65026c1b9eaf"},
|
||||
{file = "ruff-0.5.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00817603822a3e42b80f7c3298c8269e09f889ee94640cd1fc7f9329788d7bf8"},
|
||||
{file = "ruff-0.5.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:187a60f555e9f865a2ff2c6984b9afeffa7158ba6e1eab56cb830404c942b0f3"},
|
||||
{file = "ruff-0.5.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe26fc46fa8c6e0ae3f47ddccfbb136253c831c3289bba044befe68f467bfb16"},
|
||||
{file = "ruff-0.5.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad25dd9c5faac95c8e9efb13e15803cd8bbf7f4600645a60ffe17c73f60779b"},
|
||||
{file = "ruff-0.5.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f70737c157d7edf749bcb952d13854e8f745cec695a01bdc6e29c29c288fc36e"},
|
||||
{file = "ruff-0.5.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:cfd7de17cef6ab559e9f5ab859f0d3296393bc78f69030967ca4d87a541b97a0"},
|
||||
{file = "ruff-0.5.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a09b43e02f76ac0145f86a08e045e2ea452066f7ba064fd6b0cdccb486f7c3e7"},
|
||||
{file = "ruff-0.5.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0b856cb19c60cd40198be5d8d4b556228e3dcd545b4f423d1ad812bfdca5884"},
|
||||
{file = "ruff-0.5.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3687d002f911e8a5faf977e619a034d159a8373514a587249cc00f211c67a091"},
|
||||
{file = "ruff-0.5.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ac9dc814e510436e30d0ba535f435a7f3dc97f895f844f5b3f347ec8c228a523"},
|
||||
{file = "ruff-0.5.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:af9bdf6c389b5add40d89b201425b531e0a5cceb3cfdcc69f04d3d531c6be74f"},
|
||||
{file = "ruff-0.5.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d40a8533ed545390ef8315b8e25c4bb85739b90bd0f3fe1280a29ae364cc55d8"},
|
||||
{file = "ruff-0.5.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cab904683bf9e2ecbbe9ff235bfe056f0eba754d0168ad5407832928d579e7ab"},
|
||||
{file = "ruff-0.5.5-py3-none-win32.whl", hash = "sha256:696f18463b47a94575db635ebb4c178188645636f05e934fdf361b74edf1bb2d"},
|
||||
{file = "ruff-0.5.5-py3-none-win_amd64.whl", hash = "sha256:50f36d77f52d4c9c2f1361ccbfbd09099a1b2ea5d2b2222c586ab08885cf3445"},
|
||||
{file = "ruff-0.5.5-py3-none-win_arm64.whl", hash = "sha256:3191317d967af701f1b73a31ed5788795936e423b7acce82a2b63e26eb3e89d6"},
|
||||
{file = "ruff-0.5.5.tar.gz", hash = "sha256:cc5516bdb4858d972fbc31d246bdb390eab8df1a26e2353be2dbc0c2d7f5421a"},
|
||||
{file = "ruff-0.6.2-py3-none-linux_armv6l.whl", hash = "sha256:5c8cbc6252deb3ea840ad6a20b0f8583caab0c5ef4f9cca21adc5a92b8f79f3c"},
|
||||
{file = "ruff-0.6.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:17002fe241e76544448a8e1e6118abecbe8cd10cf68fde635dad480dba594570"},
|
||||
{file = "ruff-0.6.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3dbeac76ed13456f8158b8f4fe087bf87882e645c8e8b606dd17b0b66c2c1158"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:094600ee88cda325988d3f54e3588c46de5c18dae09d683ace278b11f9d4d534"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:316d418fe258c036ba05fbf7dfc1f7d3d4096db63431546163b472285668132b"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d72b8b3abf8a2d51b7b9944a41307d2f442558ccb3859bbd87e6ae9be1694a5d"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2aed7e243be68487aa8982e91c6e260982d00da3f38955873aecd5a9204b1d66"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d371f7fc9cec83497fe7cf5eaf5b76e22a8efce463de5f775a1826197feb9df8"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8f310d63af08f583363dfb844ba8f9417b558199c58a5999215082036d795a1"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7db6880c53c56addb8638fe444818183385ec85eeada1d48fc5abe045301b2f1"},
|
||||
{file = "ruff-0.6.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1175d39faadd9a50718f478d23bfc1d4da5743f1ab56af81a2b6caf0a2394f23"},
|
||||
{file = "ruff-0.6.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b939f9c86d51635fe486585389f54582f0d65b8238e08c327c1534844b3bb9a"},
|
||||
{file = "ruff-0.6.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d0d62ca91219f906caf9b187dea50d17353f15ec9bb15aae4a606cd697b49b4c"},
|
||||
{file = "ruff-0.6.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7438a7288f9d67ed3c8ce4d059e67f7ed65e9fe3aa2ab6f5b4b3610e57e3cb56"},
|
||||
{file = "ruff-0.6.2-py3-none-win32.whl", hash = "sha256:279d5f7d86696df5f9549b56b9b6a7f6c72961b619022b5b7999b15db392a4da"},
|
||||
{file = "ruff-0.6.2-py3-none-win_amd64.whl", hash = "sha256:d9f3469c7dd43cd22eb1c3fc16926fb8258d50cb1b216658a07be95dd117b0f2"},
|
||||
{file = "ruff-0.6.2-py3-none-win_arm64.whl", hash = "sha256:f28fcd2cd0e02bdf739297516d5643a945cc7caf09bd9bcb4d932540a5ea4fa9"},
|
||||
{file = "ruff-0.6.2.tar.gz", hash = "sha256:239ee6beb9e91feb8e0ec384204a763f36cb53fb895a1a364618c6abb076b3be"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3190,4 +3104,4 @@ user-search = ["pyicu"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.8.0"
|
||||
content-hash = "c165cdc1f6612c9f1b5bfd8063c23e2d595d717dd8ac1a468519e902be2cdf93"
|
||||
content-hash = "2bf09e2b68f3abd1a0f9ff2227eb3026ac3d034845acfc120d0b1cb8167ea43b"
|
||||
|
||||
-280
@@ -1,280 +0,0 @@
|
||||
[MASTER]
|
||||
|
||||
# Specify a configuration file.
|
||||
#rcfile=
|
||||
|
||||
# Python code to execute, usually for sys.path manipulation such as
|
||||
# pygtk.require().
|
||||
#init-hook=
|
||||
|
||||
# Profiled execution.
|
||||
profile=no
|
||||
|
||||
# Add files or directories to the blacklist. They should be base names, not
|
||||
# paths.
|
||||
ignore=CVS
|
||||
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=yes
|
||||
|
||||
# List of plugins (as comma separated values of python modules names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time. See also the "--disable" option for examples.
|
||||
#enable=
|
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You
|
||||
# can either give multiple identifiers separated by comma (,) or put this
|
||||
# option multiple times (only on the command line, not in the configuration
|
||||
# file where it should appear only once).You can also use "--disable=all" to
|
||||
# disable everything first and then reenable specific checks. For example, if
|
||||
# you want to run only the similarities checker, you can use "--disable=all
|
||||
# --enable=similarities". If you want to run only the classes checker, but have
|
||||
# no Warning level messages displayed, use"--disable=all --enable=classes
|
||||
# --disable=W"
|
||||
disable=missing-docstring
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, msvs
|
||||
# (visual studio) and html. You can also give a reporter class, eg
|
||||
# mypackage.mymodule.MyReporterClass.
|
||||
output-format=text
|
||||
|
||||
# Put messages in a separate file for each module / package specified on the
|
||||
# command line instead of printing them on stdout. Reports (if any) will be
|
||||
# written in a file name "pylint_global.[txt|html]".
|
||||
files-output=no
|
||||
|
||||
# Tells whether to display a full report or only the messages
|
||||
reports=yes
|
||||
|
||||
# Python expression which should return a note less than 10 (10 is the highest
|
||||
# note). You have access to the variables errors warning, statement which
|
||||
# respectively contain the number of errors / warnings messages and the total
|
||||
# number of statements analyzed. This is used by the global evaluation report
|
||||
# (RP0004).
|
||||
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
||||
|
||||
# Add a comment according to your evaluation note. This is used by the global
|
||||
# evaluation report (RP0004).
|
||||
comment=no
|
||||
|
||||
# Template used to display messages. This is a python new-style format string
|
||||
# used to format the message information. See doc for all details
|
||||
#msg-template=
|
||||
|
||||
|
||||
[TYPECHECK]
|
||||
|
||||
# Tells whether missing members accessed in mixin class should be ignored. A
|
||||
# mixin class is detected if its name ends with "mixin" (case insensitive).
|
||||
ignore-mixin-members=yes
|
||||
|
||||
# List of classes names for which member attributes should not be checked
|
||||
# (useful for classes with attributes dynamically set).
|
||||
ignored-classes=SQLObject
|
||||
|
||||
# When zope mode is activated, add a predefined set of Zope acquired attributes
|
||||
# to generated-members.
|
||||
zope=no
|
||||
|
||||
# List of members which are set dynamically and missed by pylint inference
|
||||
# system, and so shouldn't trigger E0201 when accessed. Python regular
|
||||
# expressions are accepted.
|
||||
generated-members=REQUEST,acl_users,aq_parent
|
||||
|
||||
|
||||
[MISCELLANEOUS]
|
||||
|
||||
# List of note tags to take in consideration, separated by a comma.
|
||||
notes=FIXME,XXX,TODO
|
||||
|
||||
|
||||
[SIMILARITIES]
|
||||
|
||||
# Minimum lines number of a similarity.
|
||||
min-similarity-lines=4
|
||||
|
||||
# Ignore comments when computing similarities.
|
||||
ignore-comments=yes
|
||||
|
||||
# Ignore docstrings when computing similarities.
|
||||
ignore-docstrings=yes
|
||||
|
||||
# Ignore imports when computing similarities.
|
||||
ignore-imports=no
|
||||
|
||||
|
||||
[VARIABLES]
|
||||
|
||||
# Tells whether we should check for unused import in __init__ files.
|
||||
init-import=no
|
||||
|
||||
# A regular expression matching the beginning of the name of dummy variables
|
||||
# (i.e. not used).
|
||||
dummy-variables-rgx=_$|dummy
|
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember that
|
||||
# you should avoid to define new builtins when possible.
|
||||
additional-builtins=
|
||||
|
||||
|
||||
[BASIC]
|
||||
|
||||
# Required attributes for module, separated by a comma
|
||||
required-attributes=
|
||||
|
||||
# List of builtins function names that should not be used, separated by a comma
|
||||
bad-functions=map,filter,apply,input
|
||||
|
||||
# Regular expression which should only match correct module names
|
||||
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
||||
|
||||
# Regular expression which should only match correct module level names
|
||||
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
||||
|
||||
# Regular expression which should only match correct class names
|
||||
class-rgx=[A-Z_][a-zA-Z0-9]+$
|
||||
|
||||
# Regular expression which should only match correct function names
|
||||
function-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct method names
|
||||
method-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct instance attribute names
|
||||
attr-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct argument names
|
||||
argument-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct variable names
|
||||
variable-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct attribute names in class
|
||||
# bodies
|
||||
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
|
||||
|
||||
# Regular expression which should only match correct list comprehension /
|
||||
# generator expression variable names
|
||||
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
|
||||
|
||||
# Good variable names which should always be accepted, separated by a comma
|
||||
good-names=i,j,k,ex,Run,_
|
||||
|
||||
# Bad variable names which should always be refused, separated by a comma
|
||||
bad-names=foo,bar,baz,toto,tutu,tata
|
||||
|
||||
# Regular expression which should only match function or class names that do
|
||||
# not require a docstring.
|
||||
no-docstring-rgx=__.*__
|
||||
|
||||
# Minimum line length for functions/classes that require docstrings, shorter
|
||||
# ones are exempt.
|
||||
docstring-min-length=-1
|
||||
|
||||
|
||||
[FORMAT]
|
||||
|
||||
# Maximum number of characters on a single line.
|
||||
max-line-length=80
|
||||
|
||||
# Regexp for a line that is allowed to be longer than the limit.
|
||||
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
|
||||
|
||||
# Allow the body of an if to be on the same line as the test if there is no
|
||||
# else.
|
||||
single-line-if-stmt=no
|
||||
|
||||
# List of optional constructs for which whitespace checking is disabled
|
||||
no-space-check=trailing-comma,dict-separator
|
||||
|
||||
# Maximum number of lines in a module
|
||||
max-module-lines=1000
|
||||
|
||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||
# tab).
|
||||
indent-string=' '
|
||||
|
||||
|
||||
[DESIGN]
|
||||
|
||||
# Maximum number of arguments for function / method
|
||||
max-args=5
|
||||
|
||||
# Argument names that match this expression will be ignored. Default to name
|
||||
# with leading underscore
|
||||
ignored-argument-names=_.*
|
||||
|
||||
# Maximum number of locals for function / method body
|
||||
max-locals=15
|
||||
|
||||
# Maximum number of return / yield for function / method body
|
||||
max-returns=6
|
||||
|
||||
# Maximum number of branch for function / method body
|
||||
max-branches=12
|
||||
|
||||
# Maximum number of statements in function / method body
|
||||
max-statements=50
|
||||
|
||||
# Maximum number of parents for a class (see R0901).
|
||||
max-parents=7
|
||||
|
||||
# Maximum number of attributes for a class (see R0902).
|
||||
max-attributes=7
|
||||
|
||||
# Minimum number of public methods for a class (see R0903).
|
||||
min-public-methods=2
|
||||
|
||||
# Maximum number of public methods for a class (see R0904).
|
||||
max-public-methods=20
|
||||
|
||||
|
||||
[IMPORTS]
|
||||
|
||||
# Deprecated modules which should not be used, separated by a comma
|
||||
deprecated-modules=regsub,TERMIOS,Bastion,rexec
|
||||
|
||||
# Create a graph of every (i.e. internal and external) dependencies in the
|
||||
# given file (report RP0402 must not be disabled)
|
||||
import-graph=
|
||||
|
||||
# Create a graph of external dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
ext-import-graph=
|
||||
|
||||
# Create a graph of internal dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
int-import-graph=
|
||||
|
||||
|
||||
[CLASSES]
|
||||
|
||||
# List of interface methods to ignore, separated by a comma. This is used for
|
||||
# instance to not check methods defines in Zope's Interface base class.
|
||||
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
|
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes.
|
||||
defining-attr-methods=__init__,__new__,setUp
|
||||
|
||||
# List of valid names for the first argument in a class method.
|
||||
valid-classmethod-first-arg=cls
|
||||
|
||||
# List of valid names for the first argument in a metaclass class method.
|
||||
valid-metaclass-classmethod-first-arg=mcs
|
||||
|
||||
|
||||
[EXCEPTIONS]
|
||||
|
||||
# Exceptions that will emit a warning when being caught. Defaults to
|
||||
# "Exception"
|
||||
overgeneral-exceptions=Exception
|
||||
+18
-20
@@ -34,14 +34,9 @@
|
||||
name = "Internal Changes"
|
||||
showcontent = true
|
||||
|
||||
[tool.black]
|
||||
target-version = ['py38', 'py39', 'py310', 'py311']
|
||||
# black ignores everything in .gitignore by default, see
|
||||
# https://black.readthedocs.io/en/stable/usage_and_configuration/file_collection_and_discovery.html#gitignore
|
||||
# Use `extend-exclude` if you want to exclude something in addition to this.
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
target-version = "py38"
|
||||
|
||||
[tool.ruff.lint]
|
||||
# See https://beta.ruff.rs/docs/rules/#error-e
|
||||
@@ -63,6 +58,8 @@ select = [
|
||||
"W",
|
||||
# pyflakes
|
||||
"F",
|
||||
# isort
|
||||
"I001",
|
||||
# flake8-bugbear
|
||||
"B0",
|
||||
# flake8-comprehensions
|
||||
@@ -79,17 +76,20 @@ select = [
|
||||
"EXE",
|
||||
]
|
||||
|
||||
[tool.isort]
|
||||
line_length = 88
|
||||
sections = ["FUTURE", "STDLIB", "THIRDPARTY", "TWISTED", "FIRSTPARTY", "TESTS", "LOCALFOLDER"]
|
||||
default_section = "THIRDPARTY"
|
||||
known_first_party = ["synapse"]
|
||||
known_tests = ["tests"]
|
||||
known_twisted = ["twisted", "OpenSSL"]
|
||||
multi_line_output = 3
|
||||
include_trailing_comma = true
|
||||
combine_as_imports = true
|
||||
skip_gitignore = true
|
||||
[tool.ruff.lint.isort]
|
||||
combine-as-imports = true
|
||||
section-order = ["future", "standard-library", "third-party", "twisted", "first-party", "testing", "local-folder"]
|
||||
known-first-party = ["synapse"]
|
||||
|
||||
[tool.ruff.lint.isort.sections]
|
||||
twisted = ["twisted", "OpenSSL"]
|
||||
testing = ["tests"]
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "double"
|
||||
indent-style = "space"
|
||||
skip-magic-trailing-comma = false
|
||||
line-ending = "auto"
|
||||
|
||||
[tool.maturin]
|
||||
manifest-path = "rust/Cargo.toml"
|
||||
@@ -320,9 +320,7 @@ all = [
|
||||
# failing on new releases. Keeping lower bounds loose here means that dependabot
|
||||
# can bump versions without having to update the content-hash in the lockfile.
|
||||
# This helps prevents merge conflicts when running a batch of dependabot updates.
|
||||
isort = ">=5.10.1"
|
||||
black = ">=22.7.0"
|
||||
ruff = "0.5.5"
|
||||
ruff = "0.6.2"
|
||||
# Type checking only works with the pydantic.v1 compat module from pydantic v2
|
||||
pydantic = "^2"
|
||||
|
||||
|
||||
+2
-7
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Runs linting scripts over the local Synapse checkout
|
||||
# black - opinionated code formatter
|
||||
# ruff - lints and finds mistakes
|
||||
# mypy - typechecks python code
|
||||
# cargo clippy - lints rust code
|
||||
|
||||
set -e
|
||||
|
||||
@@ -101,12 +102,6 @@ echo
|
||||
# Print out the commands being run
|
||||
set -x
|
||||
|
||||
# Ensure the sort order of imports.
|
||||
isort "${files[@]}"
|
||||
|
||||
# Ensure Python code conforms to an opinionated style.
|
||||
python3 -m black "${files[@]}"
|
||||
|
||||
# Ensure the sample configuration file conforms to style checks.
|
||||
./scripts-dev/config-lint.sh
|
||||
|
||||
|
||||
@@ -121,7 +121,9 @@ class MSC3861DelegatedAuth(BaseAuth):
|
||||
self._hostname = hs.hostname
|
||||
self._admin_token = self._config.admin_token
|
||||
|
||||
self._issuer_metadata = RetryOnExceptionCachedCall(self._load_metadata)
|
||||
self._issuer_metadata = RetryOnExceptionCachedCall[OpenIDProviderMetadata](
|
||||
self._load_metadata
|
||||
)
|
||||
|
||||
if isinstance(auth_method, PrivateKeyJWTWithKid):
|
||||
# Use the JWK as the client secret when using the private_key_jwt method
|
||||
@@ -145,6 +147,33 @@ class MSC3861DelegatedAuth(BaseAuth):
|
||||
# metadata.validate_introspection_endpoint()
|
||||
return metadata
|
||||
|
||||
async def issuer(self) -> str:
|
||||
"""
|
||||
Get the configured issuer
|
||||
|
||||
This will use the issuer value set in the metadata,
|
||||
falling back to the one set in the config if not set in the metadata
|
||||
"""
|
||||
metadata = await self._issuer_metadata.get()
|
||||
return metadata.issuer or self._config.issuer
|
||||
|
||||
async def account_management_url(self) -> Optional[str]:
|
||||
"""
|
||||
Get the configured account management URL
|
||||
|
||||
This will discover the account management URL from the issuer if it's not set in the config
|
||||
"""
|
||||
if self._config.account_management_url is not None:
|
||||
return self._config.account_management_url
|
||||
|
||||
try:
|
||||
metadata = await self._issuer_metadata.get()
|
||||
return metadata.get("account_management_uri", None)
|
||||
# We don't want to raise here if we can't load the metadata
|
||||
except Exception:
|
||||
logger.warning("Failed to load metadata:", exc_info=True)
|
||||
return None
|
||||
|
||||
async def _introspection_endpoint(self) -> str:
|
||||
"""
|
||||
Returns the introspection endpoint of the issuer
|
||||
@@ -154,7 +183,7 @@ class MSC3861DelegatedAuth(BaseAuth):
|
||||
if self._config.introspection_endpoint is not None:
|
||||
return self._config.introspection_endpoint
|
||||
|
||||
metadata = await self._load_metadata()
|
||||
metadata = await self._issuer_metadata.get()
|
||||
return metadata.get("introspection_endpoint")
|
||||
|
||||
async def _introspect_token(self, token: str) -> IntrospectionToken:
|
||||
|
||||
@@ -230,6 +230,8 @@ class EventContentFields:
|
||||
|
||||
ROOM_NAME: Final = "name"
|
||||
|
||||
MEMBERSHIP: Final = "membership"
|
||||
|
||||
# Used in m.room.guest_access events.
|
||||
GUEST_ACCESS: Final = "guest_access"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,14 +20,14 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, cast
|
||||
|
||||
from twisted.web.server import Request
|
||||
|
||||
from synapse.api.constants import LoginType
|
||||
from synapse.api.errors import LoginError, SynapseError
|
||||
from synapse.api.urls import CLIENT_API_PREFIX
|
||||
from synapse.http.server import HttpServer, respond_with_html
|
||||
from synapse.http.server import HttpServer, respond_with_html, respond_with_redirect
|
||||
from synapse.http.servlet import RestServlet, parse_string
|
||||
from synapse.http.site import SynapseRequest
|
||||
|
||||
@@ -66,6 +66,23 @@ class AuthRestServlet(RestServlet):
|
||||
if not session:
|
||||
raise SynapseError(400, "No session supplied")
|
||||
|
||||
if (
|
||||
self.hs.config.experimental.msc3861.enabled
|
||||
and stagetype == "org.matrix.cross_signing_reset"
|
||||
):
|
||||
# If MSC3861 is enabled, we can assume self._auth is an instance of MSC3861DelegatedAuth
|
||||
# We import lazily here because of the authlib requirement
|
||||
from synapse.api.auth.msc3861_delegated import MSC3861DelegatedAuth
|
||||
|
||||
auth = cast(MSC3861DelegatedAuth, self.auth)
|
||||
|
||||
url = await auth.account_management_url()
|
||||
if url is not None:
|
||||
url = f"{url}?action=org.matrix.cross_signing_reset"
|
||||
else:
|
||||
url = await auth.issuer()
|
||||
respond_with_redirect(request, str.encode(url))
|
||||
|
||||
if stagetype == LoginType.RECAPTCHA:
|
||||
html = self.recaptcha_template.render(
|
||||
session=session,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# limitations under the License.
|
||||
import logging
|
||||
import typing
|
||||
from typing import Tuple
|
||||
from typing import Tuple, cast
|
||||
|
||||
from synapse.api.errors import Codes, SynapseError
|
||||
from synapse.http.server import HttpServer
|
||||
@@ -43,10 +43,16 @@ class AuthIssuerServlet(RestServlet):
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
super().__init__()
|
||||
self._config = hs.config
|
||||
self._auth = hs.get_auth()
|
||||
|
||||
async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
|
||||
if self._config.experimental.msc3861.enabled:
|
||||
return 200, {"issuer": self._config.experimental.msc3861.issuer}
|
||||
# If MSC3861 is enabled, we can assume self._auth is an instance of MSC3861DelegatedAuth
|
||||
# We import lazily here because of the authlib requirement
|
||||
from synapse.api.auth.msc3861_delegated import MSC3861DelegatedAuth
|
||||
|
||||
auth = cast(MSC3861DelegatedAuth, self._auth)
|
||||
return 200, {"issuer": await auth.issuer()}
|
||||
else:
|
||||
# Wouldn't expect this to be reached: the servelet shouldn't have been
|
||||
# registered. Still, fail gracefully if we are registered for some reason.
|
||||
|
||||
+35
-13
@@ -23,10 +23,13 @@
|
||||
import logging
|
||||
import re
|
||||
from collections import Counter
|
||||
from http import HTTPStatus
|
||||
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple
|
||||
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, cast
|
||||
|
||||
from synapse.api.errors import Codes, InvalidAPICallError, SynapseError
|
||||
from synapse.api.errors import (
|
||||
InteractiveAuthIncompleteError,
|
||||
InvalidAPICallError,
|
||||
SynapseError,
|
||||
)
|
||||
from synapse.http.server import HttpServer
|
||||
from synapse.http.servlet import (
|
||||
RestServlet,
|
||||
@@ -403,17 +406,36 @@ class SigningKeyUploadServlet(RestServlet):
|
||||
# explicitly mark the master key as replaceable.
|
||||
if self.hs.config.experimental.msc3861.enabled:
|
||||
if not master_key_updatable_without_uia:
|
||||
config = self.hs.config.experimental.msc3861
|
||||
if config.account_management_url is not None:
|
||||
url = f"{config.account_management_url}?action=org.matrix.cross_signing_reset"
|
||||
else:
|
||||
url = config.issuer
|
||||
# If MSC3861 is enabled, we can assume self.auth is an instance of MSC3861DelegatedAuth
|
||||
# We import lazily here because of the authlib requirement
|
||||
from synapse.api.auth.msc3861_delegated import MSC3861DelegatedAuth
|
||||
|
||||
raise SynapseError(
|
||||
HTTPStatus.NOT_IMPLEMENTED,
|
||||
"To reset your end-to-end encryption cross-signing identity, "
|
||||
f"you first need to approve it at {url} and then try again.",
|
||||
Codes.UNRECOGNIZED,
|
||||
auth = cast(MSC3861DelegatedAuth, self.auth)
|
||||
|
||||
uri = await auth.account_management_url()
|
||||
if uri is not None:
|
||||
url = f"{uri}?action=org.matrix.cross_signing_reset"
|
||||
else:
|
||||
url = await auth.issuer()
|
||||
|
||||
# We use a dummy session ID as this isn't really a UIA flow, but we
|
||||
# reuse the same API shape for better client compatibility.
|
||||
raise InteractiveAuthIncompleteError(
|
||||
"dummy",
|
||||
{
|
||||
"session": "dummy",
|
||||
"flows": [
|
||||
{"stages": ["org.matrix.cross_signing_reset"]},
|
||||
],
|
||||
"params": {
|
||||
"org.matrix.cross_signing_reset": {
|
||||
"url": url,
|
||||
},
|
||||
},
|
||||
"msg": "To reset your end-to-end encryption cross-signing "
|
||||
f"identity, you first need to approve it at {url} and "
|
||||
"then try again.",
|
||||
},
|
||||
)
|
||||
else:
|
||||
# Without MSC3861, we require UIA.
|
||||
|
||||
@@ -268,7 +268,7 @@ class LoginRestServlet(RestServlet):
|
||||
approval_notice_medium=ApprovalNoticeMedium.NONE,
|
||||
)
|
||||
|
||||
well_known_data = self._well_known_builder.get_well_known()
|
||||
well_known_data = await self._well_known_builder.get_well_known()
|
||||
if well_known_data:
|
||||
result["well_known"] = well_known_data
|
||||
return 200, result
|
||||
|
||||
@@ -28,7 +28,7 @@ from synapse._pydantic_compat import HAS_PYDANTIC_V2
|
||||
if TYPE_CHECKING or HAS_PYDANTIC_V2:
|
||||
from pydantic.v1 import Extra, StrictInt, StrictStr
|
||||
else:
|
||||
from pydantic import StrictInt, StrictStr, Extra
|
||||
from pydantic import Extra, StrictInt, StrictStr
|
||||
|
||||
from signedjson.sign import sign_json
|
||||
|
||||
|
||||
+21
-16
@@ -18,12 +18,13 @@
|
||||
#
|
||||
#
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from typing import TYPE_CHECKING, Optional, Tuple, cast
|
||||
|
||||
from twisted.web.resource import Resource
|
||||
from twisted.web.server import Request
|
||||
|
||||
from synapse.http.server import set_cors_headers
|
||||
from synapse.api.errors import NotFoundError
|
||||
from synapse.http.server import DirectServeJsonResource
|
||||
from synapse.http.site import SynapseRequest
|
||||
from synapse.types import JsonDict
|
||||
from synapse.util import json_encoder
|
||||
@@ -38,8 +39,9 @@ logger = logging.getLogger(__name__)
|
||||
class WellKnownBuilder:
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self._config = hs.config
|
||||
self._auth = hs.get_auth()
|
||||
|
||||
def get_well_known(self) -> Optional[JsonDict]:
|
||||
async def get_well_known(self) -> Optional[JsonDict]:
|
||||
if not self._config.server.serve_client_wellknown:
|
||||
return None
|
||||
|
||||
@@ -52,13 +54,20 @@ class WellKnownBuilder:
|
||||
|
||||
# We use the MSC3861 values as they are used by multiple MSCs
|
||||
if self._config.experimental.msc3861.enabled:
|
||||
# If MSC3861 is enabled, we can assume self._auth is an instance of MSC3861DelegatedAuth
|
||||
# We import lazily here because of the authlib requirement
|
||||
from synapse.api.auth.msc3861_delegated import MSC3861DelegatedAuth
|
||||
|
||||
auth = cast(MSC3861DelegatedAuth, self._auth)
|
||||
|
||||
result["org.matrix.msc2965.authentication"] = {
|
||||
"issuer": self._config.experimental.msc3861.issuer
|
||||
"issuer": await auth.issuer(),
|
||||
}
|
||||
if self._config.experimental.msc3861.account_management_url is not None:
|
||||
account_management_url = await auth.account_management_url()
|
||||
if account_management_url is not None:
|
||||
result["org.matrix.msc2965.authentication"][
|
||||
"account"
|
||||
] = self._config.experimental.msc3861.account_management_url
|
||||
] = account_management_url
|
||||
|
||||
if self._config.server.extra_well_known_client_content:
|
||||
for (
|
||||
@@ -71,26 +80,22 @@ class WellKnownBuilder:
|
||||
return result
|
||||
|
||||
|
||||
class ClientWellKnownResource(Resource):
|
||||
class ClientWellKnownResource(DirectServeJsonResource):
|
||||
"""A Twisted web resource which renders the .well-known/matrix/client file"""
|
||||
|
||||
isLeaf = 1
|
||||
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
Resource.__init__(self)
|
||||
super().__init__()
|
||||
self._well_known_builder = WellKnownBuilder(hs)
|
||||
|
||||
def render_GET(self, request: SynapseRequest) -> bytes:
|
||||
set_cors_headers(request)
|
||||
r = self._well_known_builder.get_well_known()
|
||||
async def _async_render_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
|
||||
r = await self._well_known_builder.get_well_known()
|
||||
if not r:
|
||||
request.setResponseCode(404)
|
||||
request.setHeader(b"Content-Type", b"text/plain")
|
||||
return b".well-known not available"
|
||||
raise NotFoundError(".well-known not available")
|
||||
|
||||
logger.debug("returning: %s", r)
|
||||
request.setHeader(b"Content-Type", b"application/json")
|
||||
return json_encoder.encode(r).encode("utf-8")
|
||||
return 200, r
|
||||
|
||||
|
||||
class ServerWellKnownResource(Resource):
|
||||
|
||||
@@ -23,8 +23,11 @@ import logging
|
||||
from abc import ABCMeta
|
||||
from typing import TYPE_CHECKING, Any, Collection, Dict, Iterable, Optional, Union
|
||||
|
||||
from synapse.storage.database import make_in_list_sql_clause # noqa: F401; noqa: F401
|
||||
from synapse.storage.database import DatabasePool, LoggingDatabaseConnection
|
||||
from synapse.storage.database import (
|
||||
DatabasePool,
|
||||
LoggingDatabaseConnection,
|
||||
make_in_list_sql_clause, # noqa: F401
|
||||
)
|
||||
from synapse.types import get_domain_from_id
|
||||
from synapse.util import json_decoder
|
||||
from synapse.util.caches.descriptors import CachedFunction
|
||||
@@ -123,6 +126,9 @@ class SQLBaseStore(metaclass=ABCMeta):
|
||||
self._attempt_to_invalidate_cache(
|
||||
"_get_rooms_for_local_user_where_membership_is_inner", (user_id,)
|
||||
)
|
||||
self._attempt_to_invalidate_cache(
|
||||
"get_sliding_sync_rooms_for_user", (user_id,)
|
||||
)
|
||||
|
||||
# Purge other caches based on room state.
|
||||
self._attempt_to_invalidate_cache("get_room_summary", (room_id,))
|
||||
@@ -157,6 +163,7 @@ class SQLBaseStore(metaclass=ABCMeta):
|
||||
self._attempt_to_invalidate_cache("get_room_summary", (room_id,))
|
||||
self._attempt_to_invalidate_cache("get_room_type", (room_id,))
|
||||
self._attempt_to_invalidate_cache("get_room_encryption", (room_id,))
|
||||
self._attempt_to_invalidate_cache("get_sliding_sync_rooms_for_user", None)
|
||||
|
||||
def _attempt_to_invalidate_cache(
|
||||
self, cache_name: str, key: Optional[Collection[Any]]
|
||||
|
||||
@@ -44,7 +44,7 @@ from synapse._pydantic_compat import HAS_PYDANTIC_V2
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.storage.engines import PostgresEngine
|
||||
from synapse.storage.types import Connection, Cursor
|
||||
from synapse.types import JsonDict
|
||||
from synapse.types import JsonDict, StrCollection
|
||||
from synapse.util import Clock, json_encoder
|
||||
|
||||
from . import engines
|
||||
@@ -487,6 +487,25 @@ class BackgroundUpdater:
|
||||
|
||||
return not update_exists
|
||||
|
||||
async def have_completed_background_updates(
|
||||
self, update_names: StrCollection
|
||||
) -> bool:
|
||||
"""Return the name of background updates that have not yet been
|
||||
completed"""
|
||||
if self._all_done:
|
||||
return True
|
||||
|
||||
rows = await self.db_pool.simple_select_many_batch(
|
||||
table="background_updates",
|
||||
column="update_name",
|
||||
iterable=update_names,
|
||||
retcols=("update_name",),
|
||||
desc="get_uncompleted_background_updates",
|
||||
)
|
||||
|
||||
# If we find any rows then we've not completed the update.
|
||||
return not bool(rows)
|
||||
|
||||
async def do_next_background_update(self, sleep: bool = True) -> bool:
|
||||
"""Does some amount of work on the next queued background update
|
||||
|
||||
|
||||
@@ -346,6 +346,9 @@ class CacheInvalidationWorkerStore(SQLBaseStore):
|
||||
self._attempt_to_invalidate_cache(
|
||||
"_get_rooms_for_local_user_where_membership_is_inner", (state_key,)
|
||||
)
|
||||
self._attempt_to_invalidate_cache(
|
||||
"get_sliding_sync_rooms_for_user", (state_key,)
|
||||
)
|
||||
|
||||
self._attempt_to_invalidate_cache(
|
||||
"did_forget",
|
||||
@@ -417,6 +420,7 @@ class CacheInvalidationWorkerStore(SQLBaseStore):
|
||||
self._attempt_to_invalidate_cache(
|
||||
"_get_rooms_for_local_user_where_membership_is_inner", None
|
||||
)
|
||||
self._attempt_to_invalidate_cache("get_sliding_sync_rooms_for_user", None)
|
||||
self._attempt_to_invalidate_cache("did_forget", None)
|
||||
self._attempt_to_invalidate_cache("get_forgotten_rooms_for_user", None)
|
||||
self._attempt_to_invalidate_cache("get_references_for_event", None)
|
||||
|
||||
@@ -163,6 +163,15 @@ class SlidingSyncMembershipInfo:
|
||||
sender: str
|
||||
membership_event_id: str
|
||||
membership: str
|
||||
|
||||
|
||||
@attr.s(slots=True, auto_attribs=True)
|
||||
class SlidingSyncMembershipInfoWithEventPos(SlidingSyncMembershipInfo):
|
||||
"""
|
||||
SlidingSyncMembershipInfo + `stream_ordering`/`instance_name` of the membership
|
||||
event
|
||||
"""
|
||||
|
||||
membership_event_stream_ordering: int
|
||||
membership_event_instance_name: str
|
||||
|
||||
@@ -170,17 +179,6 @@ class SlidingSyncMembershipInfo:
|
||||
@attr.s(slots=True, auto_attribs=True)
|
||||
class SlidingSyncTableChanges:
|
||||
room_id: str
|
||||
# `stream_ordering` of the most recent event being persisted in the room. This doesn't
|
||||
# need to be perfect, we just need *some* answer that points to a real event in the
|
||||
# room in case we are the first ones inserting into the `sliding_sync_joined_rooms`
|
||||
# table because of the `NON NULL` constraint on `event_stream_ordering`. In reality,
|
||||
# `_update_sliding_sync_tables_with_new_persisted_events_txn()` is run after
|
||||
# `_update_current_state_txn()` whenever a new event is persisted to update it to the
|
||||
# correct latest value.
|
||||
#
|
||||
# This should be *some* value that points to a real event in the room if we are
|
||||
# still joined to the room and some state is changing (`to_insert` or `to_delete`).
|
||||
joined_room_best_effort_most_recent_stream_ordering: Optional[int]
|
||||
# If the row doesn't exist in the `sliding_sync_joined_rooms` table, we need to
|
||||
# fully-insert it which means we also need to include a `bump_stamp` value to use
|
||||
# for the row. This should only be populated when we're trying to fully-insert a
|
||||
@@ -401,6 +399,9 @@ class PersistEventsStore:
|
||||
`stream_ordering`).
|
||||
delta_state: Deltas that are going to be used to update the
|
||||
`current_state_events` table. Changes to the current state of the room.
|
||||
|
||||
Returns:
|
||||
SlidingSyncTableChanges
|
||||
"""
|
||||
to_insert = delta_state.to_insert
|
||||
to_delete = delta_state.to_delete
|
||||
@@ -410,7 +411,6 @@ class PersistEventsStore:
|
||||
if not to_insert and not to_delete:
|
||||
return SlidingSyncTableChanges(
|
||||
room_id=room_id,
|
||||
joined_room_best_effort_most_recent_stream_ordering=None,
|
||||
joined_room_bump_stamp_to_fully_insert=None,
|
||||
joined_room_updates={},
|
||||
membership_snapshot_shared_insert_values={},
|
||||
@@ -469,24 +469,24 @@ class PersistEventsStore:
|
||||
membership_event_id,
|
||||
user_id,
|
||||
) in membership_event_id_to_user_id_map.items():
|
||||
# We should only be seeing events with `stream_ordering`/`instance_name` assigned by this point
|
||||
membership_event_stream_ordering = membership_event_map[
|
||||
membership_event_id
|
||||
].internal_metadata.stream_ordering
|
||||
assert membership_event_stream_ordering is not None
|
||||
membership_event_instance_name = membership_event_map[
|
||||
membership_event_id
|
||||
].internal_metadata.instance_name
|
||||
assert membership_event_instance_name is not None
|
||||
|
||||
membership_infos_to_insert_membership_snapshots.append(
|
||||
# XXX: We don't use `SlidingSyncMembershipInfoWithEventPos` here
|
||||
# because we're sourcing the event from `events_and_contexts`, we
|
||||
# can't rely on `stream_ordering`/`instance_name` being correct. We
|
||||
# could be working with events that were previously persisted as an
|
||||
# `outlier` with one `stream_ordering` but are now being persisted
|
||||
# again and de-outliered and assigned a different `stream_ordering`
|
||||
# that won't end up being used. Since we call
|
||||
# `_calculate_sliding_sync_table_changes()` before
|
||||
# `_update_outliers_txn()` which fixes this discrepancy (always use
|
||||
# the `stream_ordering` from the first time it was persisted), we're
|
||||
# working with an unreliable `stream_ordering` value that will
|
||||
# possibly be unused and not make it into the `events` table.
|
||||
SlidingSyncMembershipInfo(
|
||||
user_id=user_id,
|
||||
sender=membership_event_map[membership_event_id].sender,
|
||||
membership_event_id=membership_event_id,
|
||||
membership=membership_event_map[membership_event_id].membership,
|
||||
membership_event_stream_ordering=membership_event_stream_ordering,
|
||||
membership_event_instance_name=membership_event_instance_name,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -568,7 +568,6 @@ class PersistEventsStore:
|
||||
# `_update_sliding_sync_tables_with_new_persisted_events_txn()`)
|
||||
#
|
||||
joined_room_updates: SlidingSyncStateInsertValues = {}
|
||||
best_effort_most_recent_stream_ordering: Optional[int] = None
|
||||
bump_stamp_to_fully_insert: Optional[int] = None
|
||||
if not delta_state.no_longer_in_room:
|
||||
current_state_ids_map = {}
|
||||
@@ -632,9 +631,7 @@ class PersistEventsStore:
|
||||
|
||||
# Otherwise, we need to find a couple events that we were reset to.
|
||||
if missing_event_ids:
|
||||
remaining_events = await self.store.get_events(
|
||||
current_state_ids_map.values()
|
||||
)
|
||||
remaining_events = await self.store.get_events(missing_event_ids)
|
||||
# There shouldn't be any missing events
|
||||
assert (
|
||||
remaining_events.keys() == missing_event_ids
|
||||
@@ -657,52 +654,9 @@ class PersistEventsStore:
|
||||
elif state_key == (EventTypes.Name, ""):
|
||||
joined_room_updates["room_name"] = None
|
||||
|
||||
# Figure out `best_effort_most_recent_stream_ordering`. This doesn't need to
|
||||
# be perfect, we just need *some* answer that points to a real event in the
|
||||
# room in case we are the first ones inserting into the
|
||||
# `sliding_sync_joined_rooms` table because of the `NON NULL` constraint on
|
||||
# `event_stream_ordering`. In reality,
|
||||
# `_update_sliding_sync_tables_with_new_persisted_events_txn()` is run after
|
||||
# `_update_current_state_txn()` whenever a new event is persisted to update
|
||||
# it to the correct latest value.
|
||||
#
|
||||
if len(events_and_contexts) > 0:
|
||||
# Since the list is sorted ascending by `stream_ordering`, the last event
|
||||
# should have the highest `stream_ordering`.
|
||||
best_effort_most_recent_stream_ordering = events_and_contexts[-1][
|
||||
0
|
||||
].internal_metadata.stream_ordering
|
||||
else:
|
||||
# If there are no `events_and_contexts`, we assume it's one of two scenarios:
|
||||
# 1. If there are new state `to_insert` but no `events_and_contexts`,
|
||||
# then it's a state reset.
|
||||
# 2. Otherwise, it's some partial-state room re-syncing the current state and
|
||||
# going through un-partial process.
|
||||
#
|
||||
# Either way, we assume no new events are being persisted and we can
|
||||
# find the latest already in the database. Since this is a best-effort
|
||||
# value, we don't need to be perfect although I think we're pretty close
|
||||
# here.
|
||||
most_recent_event_pos_results = (
|
||||
await self.store.get_last_event_pos_in_room(
|
||||
room_id, event_types=None
|
||||
)
|
||||
)
|
||||
assert most_recent_event_pos_results, (
|
||||
f"We should not be seeing `None` here because we are still in the room ({room_id}) and "
|
||||
+ "it should at-least have a join membership event that's keeping us here."
|
||||
)
|
||||
best_effort_most_recent_stream_ordering = most_recent_event_pos_results[
|
||||
1
|
||||
].stream
|
||||
|
||||
# We should have found a value if we are still in the room
|
||||
assert best_effort_most_recent_stream_ordering is not None
|
||||
|
||||
return SlidingSyncTableChanges(
|
||||
room_id=room_id,
|
||||
# For `sliding_sync_joined_rooms`
|
||||
joined_room_best_effort_most_recent_stream_ordering=best_effort_most_recent_stream_ordering,
|
||||
joined_room_bump_stamp_to_fully_insert=bump_stamp_to_fully_insert,
|
||||
joined_room_updates=joined_room_updates,
|
||||
# For `sliding_sync_membership_snapshots`
|
||||
@@ -1773,31 +1727,53 @@ class PersistEventsStore:
|
||||
#
|
||||
# We only need to update when one of the relevant state values has changed
|
||||
if sliding_sync_table_changes.joined_room_updates:
|
||||
# This should be *some* value that points to a real event in the room if
|
||||
# we are still joined to the room.
|
||||
assert (
|
||||
sliding_sync_table_changes.joined_room_best_effort_most_recent_stream_ordering
|
||||
is not None
|
||||
sliding_sync_updates_keys = (
|
||||
sliding_sync_table_changes.joined_room_updates.keys()
|
||||
)
|
||||
sliding_sync_updates_values = (
|
||||
sliding_sync_table_changes.joined_room_updates.values()
|
||||
)
|
||||
|
||||
self.db_pool.simple_upsert_txn(
|
||||
txn,
|
||||
table="sliding_sync_joined_rooms",
|
||||
keyvalues={"room_id": room_id},
|
||||
values=sliding_sync_table_changes.joined_room_updates,
|
||||
insertion_values={
|
||||
# The reason we're only *inserting* (not *updating*)
|
||||
# `event_stream_ordering` here is because the column has a `NON
|
||||
# NULL` constraint and we need *some* answer. And if the row
|
||||
# already exists, it already has the correct value and it's
|
||||
# better to just rely on
|
||||
# `_update_sliding_sync_tables_with_new_persisted_events_txn()`
|
||||
# to do the right thing (same for `bump_stamp`).
|
||||
"event_stream_ordering": sliding_sync_table_changes.joined_room_best_effort_most_recent_stream_ordering,
|
||||
# If we're trying to fully-insert a row, we need to provide a
|
||||
# value for `bump_stamp` if it exists for the room.
|
||||
"bump_stamp": sliding_sync_table_changes.joined_room_bump_stamp_to_fully_insert,
|
||||
},
|
||||
args: List[Any] = [
|
||||
room_id,
|
||||
room_id,
|
||||
sliding_sync_table_changes.joined_room_bump_stamp_to_fully_insert,
|
||||
]
|
||||
args.extend(iter(sliding_sync_updates_values))
|
||||
|
||||
# XXX: We use a sub-query for `stream_ordering` because it's unreliable to
|
||||
# pre-calculate from `events_and_contexts` at the time when
|
||||
# `_calculate_sliding_sync_table_changes()` is ran. We could be working
|
||||
# with events that were previously persisted as an `outlier` with one
|
||||
# `stream_ordering` but are now being persisted again and de-outliered
|
||||
# and assigned a different `stream_ordering`. Since we call
|
||||
# `_calculate_sliding_sync_table_changes()` before
|
||||
# `_update_outliers_txn()` which fixes this discrepancy (always use the
|
||||
# `stream_ordering` from the first time it was persisted), we're working
|
||||
# with an unreliable `stream_ordering` value that will possibly be
|
||||
# unused and not make it into the `events` table.
|
||||
#
|
||||
# We don't update `event_stream_ordering` `ON CONFLICT` because it's
|
||||
# simpler and we can just rely on
|
||||
# `_update_sliding_sync_tables_with_new_persisted_events_txn()` to do
|
||||
# the right thing (same for `bump_stamp`). The only reason we're
|
||||
# inserting `event_stream_ordering` here is because the column has a
|
||||
# `NON NULL` constraint and we need some answer.
|
||||
txn.execute(
|
||||
f"""
|
||||
INSERT INTO sliding_sync_joined_rooms
|
||||
(room_id, event_stream_ordering, bump_stamp, {", ".join(sliding_sync_updates_keys)})
|
||||
VALUES (
|
||||
?,
|
||||
(SELECT stream_ordering FROM events WHERE room_id = ? ORDER BY stream_ordering DESC LIMIT 1),
|
||||
?,
|
||||
{", ".join("?" for _ in sliding_sync_updates_values)}
|
||||
)
|
||||
ON CONFLICT (room_id)
|
||||
DO UPDATE SET
|
||||
{", ".join(f"{key} = EXCLUDED.{key}" for key in sliding_sync_updates_keys)}
|
||||
""",
|
||||
args,
|
||||
)
|
||||
|
||||
# We now update `local_current_membership`. We do this regardless
|
||||
@@ -1854,38 +1830,63 @@ class PersistEventsStore:
|
||||
if sliding_sync_table_changes.to_insert_membership_snapshots:
|
||||
# Update the `sliding_sync_membership_snapshots` table
|
||||
#
|
||||
# We need to insert/update regardless of whether we have `sliding_sync_snapshot_keys`
|
||||
# because there are other fields in the `ON CONFLICT` upsert to run (see
|
||||
# inherit case above for more context when this happens).
|
||||
self.db_pool.simple_upsert_many_txn(
|
||||
txn=txn,
|
||||
table="sliding_sync_membership_snapshots",
|
||||
key_names=("room_id", "user_id"),
|
||||
key_values=[
|
||||
(room_id, membership_info.user_id)
|
||||
for membership_info in sliding_sync_table_changes.to_insert_membership_snapshots
|
||||
],
|
||||
value_names=[
|
||||
"sender",
|
||||
"membership_event_id",
|
||||
"membership",
|
||||
"event_stream_ordering",
|
||||
"event_instance_name",
|
||||
]
|
||||
+ list(
|
||||
sliding_sync_table_changes.membership_snapshot_shared_insert_values.keys()
|
||||
),
|
||||
value_values=[
|
||||
sliding_sync_snapshot_keys = (
|
||||
sliding_sync_table_changes.membership_snapshot_shared_insert_values.keys()
|
||||
)
|
||||
sliding_sync_snapshot_values = (
|
||||
sliding_sync_table_changes.membership_snapshot_shared_insert_values.values()
|
||||
)
|
||||
# We need to insert/update regardless of whether we have
|
||||
# `sliding_sync_snapshot_keys` because there are other fields in the `ON
|
||||
# CONFLICT` upsert to run (see inherit case (explained in
|
||||
# `_calculate_sliding_sync_table_changes()`) for more context when this
|
||||
# happens).
|
||||
#
|
||||
# XXX: We use a sub-query for `stream_ordering` because it's unreliable to
|
||||
# pre-calculate from `events_and_contexts` at the time when
|
||||
# `_calculate_sliding_sync_table_changes()` is ran. We could be working with
|
||||
# events that were previously persisted as an `outlier` with one
|
||||
# `stream_ordering` but are now being persisted again and de-outliered and
|
||||
# assigned a different `stream_ordering` that won't end up being used. Since
|
||||
# we call `_calculate_sliding_sync_table_changes()` before
|
||||
# `_update_outliers_txn()` which fixes this discrepancy (always use the
|
||||
# `stream_ordering` from the first time it was persisted), we're working
|
||||
# with an unreliable `stream_ordering` value that will possibly be unused
|
||||
# and not make it into the `events` table.
|
||||
txn.execute_batch(
|
||||
f"""
|
||||
INSERT INTO sliding_sync_membership_snapshots
|
||||
(room_id, user_id, sender, membership_event_id, membership, event_stream_ordering, event_instance_name
|
||||
{("," + ", ".join(sliding_sync_snapshot_keys)) if sliding_sync_snapshot_keys else ""})
|
||||
VALUES (
|
||||
?, ?, ?, ?, ?,
|
||||
(SELECT stream_ordering FROM events WHERE event_id = ?),
|
||||
(SELECT COALESCE(instance_name, 'master') FROM events WHERE event_id = ?)
|
||||
{("," + ", ".join("?" for _ in sliding_sync_snapshot_values)) if sliding_sync_snapshot_values else ""}
|
||||
)
|
||||
ON CONFLICT (room_id, user_id)
|
||||
DO UPDATE SET
|
||||
sender = EXCLUDED.sender,
|
||||
membership_event_id = EXCLUDED.membership_event_id,
|
||||
membership = EXCLUDED.membership,
|
||||
event_stream_ordering = EXCLUDED.event_stream_ordering
|
||||
{("," + ", ".join(f"{key} = EXCLUDED.{key}" for key in sliding_sync_snapshot_keys)) if sliding_sync_snapshot_keys else ""}
|
||||
""",
|
||||
[
|
||||
[
|
||||
room_id,
|
||||
membership_info.user_id,
|
||||
membership_info.sender,
|
||||
membership_info.membership_event_id,
|
||||
membership_info.membership,
|
||||
membership_info.membership_event_stream_ordering,
|
||||
membership_info.membership_event_instance_name,
|
||||
# XXX: We do not use `membership_info.membership_event_stream_ordering` here
|
||||
# because it is an unreliable value. See XXX note above.
|
||||
membership_info.membership_event_id,
|
||||
# XXX: We do not use `membership_info.membership_event_instance_name` here
|
||||
# because it is an unreliable value. See XXX note above.
|
||||
membership_info.membership_event_id,
|
||||
]
|
||||
+ list(
|
||||
sliding_sync_table_changes.membership_snapshot_shared_insert_values.values()
|
||||
)
|
||||
+ list(sliding_sync_snapshot_values)
|
||||
for membership_info in sliding_sync_table_changes.to_insert_membership_snapshots
|
||||
],
|
||||
)
|
||||
|
||||
@@ -37,10 +37,11 @@ from synapse.storage.database import (
|
||||
from synapse.storage.databases.main.events import (
|
||||
SLIDING_SYNC_RELEVANT_STATE_SET,
|
||||
PersistEventsStore,
|
||||
SlidingSyncMembershipInfo,
|
||||
SlidingSyncMembershipInfoWithEventPos,
|
||||
SlidingSyncMembershipSnapshotSharedInsertValues,
|
||||
SlidingSyncStateInsertValues,
|
||||
)
|
||||
from synapse.storage.databases.main.events_worker import DatabaseCorruptionError
|
||||
from synapse.storage.databases.main.state_deltas import StateDeltasStore
|
||||
from synapse.storage.databases.main.stream import StreamWorkerStore
|
||||
from synapse.storage.types import Cursor
|
||||
@@ -1857,6 +1858,7 @@ class EventsBackgroundUpdatesStore(StreamWorkerStore, StateDeltasStore, SQLBaseS
|
||||
initial_phase = True
|
||||
|
||||
last_room_id = progress.get("last_room_id", "")
|
||||
last_user_id = progress.get("last_user_id", "")
|
||||
last_event_stream_ordering = progress["last_event_stream_ordering"]
|
||||
|
||||
def _find_memberships_to_update_txn(
|
||||
@@ -1887,11 +1889,11 @@ class EventsBackgroundUpdatesStore(StreamWorkerStore, StateDeltasStore, SQLBaseS
|
||||
FROM local_current_membership AS c
|
||||
INNER JOIN events AS e USING (event_id)
|
||||
LEFT JOIN rooms AS r ON (c.room_id = r.room_id)
|
||||
WHERE c.room_id > ?
|
||||
ORDER BY c.room_id ASC
|
||||
WHERE (c.room_id, c.user_id) > (?, ?)
|
||||
ORDER BY c.room_id ASC, c.user_id ASC
|
||||
LIMIT ?
|
||||
""",
|
||||
(last_room_id, batch_size),
|
||||
(last_room_id, last_user_id, batch_size),
|
||||
)
|
||||
elif last_event_stream_ordering is not None:
|
||||
# It's important to sort by `event_stream_ordering` *ascending* (oldest to
|
||||
@@ -1961,24 +1963,49 @@ class EventsBackgroundUpdatesStore(StreamWorkerStore, StateDeltasStore, SQLBaseS
|
||||
return 0
|
||||
|
||||
def _find_previous_membership_txn(
|
||||
txn: LoggingTransaction, room_id: str, user_id: str, stream_ordering: int
|
||||
txn: LoggingTransaction, room_id: str, user_id: str, event_id: str
|
||||
) -> Tuple[str, str]:
|
||||
# Find the previous invite/knock event before the leave event
|
||||
#
|
||||
# Here are some notes on how we landed on this query:
|
||||
#
|
||||
# We're using `topological_ordering` instead of `stream_ordering` because
|
||||
# somehow it's possible to have your `leave` event backfilled with a
|
||||
# negative `stream_ordering` and your previous `invite` event with a
|
||||
# positive `stream_ordering` so we wouldn't have a chance of finding the
|
||||
# previous membership with a naive `event_stream_ordering < ?` comparison.
|
||||
#
|
||||
# Also be careful because `room_memberships.event_stream_ordering` is
|
||||
# nullable and not always filled in. You would need to join on `events` to
|
||||
# rely on `events.stream_ordering` instead. Even though the
|
||||
# `events.stream_ordering` also doesn't have a `NOT NULL` constraint, it
|
||||
# doesn't have any rows where this is the case (checked on `matrix.org`).
|
||||
# The fact the `events.stream_ordering` is a nullable column is a holdover
|
||||
# from a rename of the column.
|
||||
#
|
||||
# You might also consider using the `event_auth` table to find the previous
|
||||
# membership, but there are cases where somehow a membership event doesn't
|
||||
# point back to the previous membership event in the auth events (unknown
|
||||
# cause).
|
||||
txn.execute(
|
||||
"""
|
||||
SELECT event_id, membership
|
||||
FROM room_memberships
|
||||
FROM room_memberships AS m
|
||||
INNER JOIN events AS e USING (room_id, event_id)
|
||||
WHERE
|
||||
room_id = ?
|
||||
AND user_id = ?
|
||||
AND event_stream_ordering < ?
|
||||
ORDER BY event_stream_ordering DESC
|
||||
AND m.user_id = ?
|
||||
AND (m.membership = ? OR m.membership = ?)
|
||||
AND e.event_id != ?
|
||||
ORDER BY e.topological_ordering DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
(
|
||||
room_id,
|
||||
user_id,
|
||||
stream_ordering,
|
||||
Membership.INVITE,
|
||||
Membership.KNOCK,
|
||||
event_id,
|
||||
),
|
||||
)
|
||||
row = txn.fetchone()
|
||||
@@ -1993,9 +2020,9 @@ class EventsBackgroundUpdatesStore(StreamWorkerStore, StateDeltasStore, SQLBaseS
|
||||
to_insert_membership_snapshots: Dict[
|
||||
Tuple[str, str], SlidingSyncMembershipSnapshotSharedInsertValues
|
||||
] = {}
|
||||
to_insert_membership_infos: Dict[Tuple[str, str], SlidingSyncMembershipInfo] = (
|
||||
{}
|
||||
)
|
||||
to_insert_membership_infos: Dict[
|
||||
Tuple[str, str], SlidingSyncMembershipInfoWithEventPos
|
||||
] = {}
|
||||
for (
|
||||
room_id,
|
||||
room_id_from_rooms_table,
|
||||
@@ -2058,36 +2085,59 @@ class EventsBackgroundUpdatesStore(StreamWorkerStore, StateDeltasStore, SQLBaseS
|
||||
# We're iterating over rooms that we are joined to so they should
|
||||
# have `current_state_events` and we should have some current state
|
||||
# for each room
|
||||
assert current_state_ids_map
|
||||
if current_state_ids_map:
|
||||
try:
|
||||
fetched_events = await self.get_events(
|
||||
current_state_ids_map.values()
|
||||
)
|
||||
except DatabaseCorruptionError as e:
|
||||
logger.warning(
|
||||
"Failed to fetch state for room '%s' due to corrupted events. Ignoring. Error: %s",
|
||||
room_id,
|
||||
e,
|
||||
)
|
||||
continue
|
||||
|
||||
fetched_events = await self.get_events(current_state_ids_map.values())
|
||||
current_state_map: StateMap[EventBase] = {
|
||||
state_key: fetched_events[event_id]
|
||||
for state_key, event_id in current_state_ids_map.items()
|
||||
# `get_events(...)` will filter out events for unknown room versions
|
||||
if event_id in fetched_events
|
||||
}
|
||||
|
||||
current_state_map: StateMap[EventBase] = {
|
||||
state_key: fetched_events[event_id]
|
||||
for state_key, event_id in current_state_ids_map.items()
|
||||
# `get_events(...)` will filter out events for unknown room versions
|
||||
if event_id in fetched_events
|
||||
}
|
||||
# Can happen for unknown room versions (old room versions that aren't known
|
||||
# anymore) since `get_events(...)` will filter out events for unknown room
|
||||
# versions
|
||||
if not current_state_map:
|
||||
continue
|
||||
|
||||
# Can happen for unknown room versions (old room versions that aren't known
|
||||
# anymore) since `get_events(...)` will filter out events for unknown room
|
||||
# versions
|
||||
if not current_state_map:
|
||||
continue
|
||||
|
||||
state_insert_values = (
|
||||
PersistEventsStore._get_sliding_sync_insert_values_from_state_map(
|
||||
state_insert_values = PersistEventsStore._get_sliding_sync_insert_values_from_state_map(
|
||||
current_state_map
|
||||
)
|
||||
)
|
||||
sliding_sync_membership_snapshots_insert_map.update(state_insert_values)
|
||||
# We should have some insert values for each room, even if they are `None`
|
||||
assert sliding_sync_membership_snapshots_insert_map
|
||||
sliding_sync_membership_snapshots_insert_map.update(
|
||||
state_insert_values
|
||||
)
|
||||
# We should have some insert values for each room, even if they are `None`
|
||||
assert sliding_sync_membership_snapshots_insert_map
|
||||
|
||||
# We have current state to work from
|
||||
sliding_sync_membership_snapshots_insert_map["has_known_state"] = True
|
||||
# We have current state to work from
|
||||
sliding_sync_membership_snapshots_insert_map["has_known_state"] = (
|
||||
True
|
||||
)
|
||||
else:
|
||||
# Although we expect every room to have a create event (even
|
||||
# past unknown room versions since we haven't supported one
|
||||
# without it), there seem to be some corrupted rooms in
|
||||
# practice that don't have the create event in the
|
||||
# `current_state_events` table. The create event does exist
|
||||
# in the events table though. We'll just say that we don't
|
||||
# know the state for these rooms and continue on with our
|
||||
# day.
|
||||
sliding_sync_membership_snapshots_insert_map["has_known_state"] = (
|
||||
False
|
||||
)
|
||||
elif membership in (Membership.INVITE, Membership.KNOCK) or (
|
||||
membership == Membership.LEAVE and is_outlier
|
||||
membership in (Membership.LEAVE, Membership.BAN) and is_outlier
|
||||
):
|
||||
invite_or_knock_event_id = membership_event_id
|
||||
invite_or_knock_membership = membership
|
||||
@@ -2098,14 +2148,14 @@ class EventsBackgroundUpdatesStore(StreamWorkerStore, StateDeltasStore, SQLBaseS
|
||||
# us a consistent view of the room state regardless of your
|
||||
# membership (i.e. the room shouldn't disappear if your using the
|
||||
# `is_encrypted` filter and you leave).
|
||||
if membership == Membership.LEAVE and is_outlier:
|
||||
if membership in (Membership.LEAVE, Membership.BAN) and is_outlier:
|
||||
invite_or_knock_event_id, invite_or_knock_membership = (
|
||||
await self.db_pool.runInteraction(
|
||||
"sliding_sync_membership_snapshots_bg_update._find_previous_membership",
|
||||
_find_previous_membership_txn,
|
||||
room_id,
|
||||
user_id,
|
||||
membership_event_stream_ordering,
|
||||
membership_event_id,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -2145,7 +2195,15 @@ class EventsBackgroundUpdatesStore(StreamWorkerStore, StateDeltasStore, SQLBaseS
|
||||
await_full_state=False,
|
||||
)
|
||||
|
||||
fetched_events = await self.get_events(state_ids_map.values())
|
||||
try:
|
||||
fetched_events = await self.get_events(state_ids_map.values())
|
||||
except DatabaseCorruptionError as e:
|
||||
logger.warning(
|
||||
"Failed to fetch state for room '%s' due to corrupted events. Ignoring. Error: %s",
|
||||
room_id,
|
||||
e,
|
||||
)
|
||||
continue
|
||||
|
||||
state_map: StateMap[EventBase] = {
|
||||
state_key: fetched_events[event_id]
|
||||
@@ -2184,15 +2242,17 @@ class EventsBackgroundUpdatesStore(StreamWorkerStore, StateDeltasStore, SQLBaseS
|
||||
to_insert_membership_snapshots[(room_id, user_id)] = (
|
||||
sliding_sync_membership_snapshots_insert_map
|
||||
)
|
||||
to_insert_membership_infos[(room_id, user_id)] = SlidingSyncMembershipInfo(
|
||||
user_id=user_id,
|
||||
sender=sender,
|
||||
membership_event_id=membership_event_id,
|
||||
membership=membership,
|
||||
membership_event_stream_ordering=membership_event_stream_ordering,
|
||||
# If instance_name is null we default to "master"
|
||||
membership_event_instance_name=membership_event_instance_name
|
||||
or "master",
|
||||
to_insert_membership_infos[(room_id, user_id)] = (
|
||||
SlidingSyncMembershipInfoWithEventPos(
|
||||
user_id=user_id,
|
||||
sender=sender,
|
||||
membership_event_id=membership_event_id,
|
||||
membership=membership,
|
||||
membership_event_stream_ordering=membership_event_stream_ordering,
|
||||
# If instance_name is null we default to "master"
|
||||
membership_event_instance_name=membership_event_instance_name
|
||||
or "master",
|
||||
)
|
||||
)
|
||||
|
||||
def _fill_table_txn(txn: LoggingTransaction) -> None:
|
||||
@@ -2257,7 +2317,7 @@ class EventsBackgroundUpdatesStore(StreamWorkerStore, StateDeltasStore, SQLBaseS
|
||||
(
|
||||
room_id,
|
||||
_room_id_from_rooms_table,
|
||||
_user_id,
|
||||
user_id,
|
||||
_sender,
|
||||
_membership_event_id,
|
||||
_membership,
|
||||
@@ -2269,8 +2329,11 @@ class EventsBackgroundUpdatesStore(StreamWorkerStore, StateDeltasStore, SQLBaseS
|
||||
progress = {
|
||||
"initial_phase": initial_phase,
|
||||
"last_room_id": room_id,
|
||||
"last_event_stream_ordering": membership_event_stream_ordering,
|
||||
"last_user_id": user_id,
|
||||
"last_event_stream_ordering": last_event_stream_ordering,
|
||||
}
|
||||
if not initial_phase:
|
||||
progress["last_event_stream_ordering"] = membership_event_stream_ordering
|
||||
|
||||
await self.db_pool.updates._background_update_progress(
|
||||
_BackgroundUpdates.SLIDING_SYNC_MEMBERSHIP_SNAPSHOTS_BG_UPDATE,
|
||||
@@ -2279,6 +2342,17 @@ class EventsBackgroundUpdatesStore(StreamWorkerStore, StateDeltasStore, SQLBaseS
|
||||
|
||||
return len(memberships_to_update_rows)
|
||||
|
||||
async def have_finished_sliding_sync_background_jobs(self) -> bool:
|
||||
"""Return if its safe to use the sliding sync membership tables."""
|
||||
|
||||
return await self.db_pool.updates.have_completed_background_updates(
|
||||
(
|
||||
_BackgroundUpdates.SLIDING_SYNC_PREFILL_JOINED_ROOMS_TO_RECALCULATE_TABLE_BG_UPDATE,
|
||||
_BackgroundUpdates.SLIDING_SYNC_JOINED_ROOMS_BG_UPDATE,
|
||||
_BackgroundUpdates.SLIDING_SYNC_MEMBERSHIP_SNAPSHOTS_BG_UPDATE,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _resolve_stale_data_in_sliding_sync_tables(
|
||||
txn: LoggingTransaction,
|
||||
|
||||
@@ -98,6 +98,26 @@ if TYPE_CHECKING:
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DatabaseCorruptionError(RuntimeError):
|
||||
"""We found an event in the DB that has a persisted event ID that doesn't
|
||||
match its computed event ID."""
|
||||
|
||||
def __init__(
|
||||
self, room_id: str, persisted_event_id: str, computed_event_id: str
|
||||
) -> None:
|
||||
self.room_id = room_id
|
||||
self.persisted_event_id = persisted_event_id
|
||||
self.computed_event_id = computed_event_id
|
||||
|
||||
message = (
|
||||
f"Database corruption: Event {persisted_event_id} in room {room_id} "
|
||||
f"from the database appears to have been modified (calculated "
|
||||
f"event id {computed_event_id})"
|
||||
)
|
||||
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
# These values are used in the `enqueue_event` and `_fetch_loop` methods to
|
||||
# control how we batch/bulk fetch events from the database.
|
||||
# The values are plucked out of thing air to make initial sync run faster
|
||||
@@ -1364,10 +1384,8 @@ class EventsWorkerStore(SQLBaseStore):
|
||||
if original_ev.event_id != event_id:
|
||||
# it's difficult to see what to do here. Pretty much all bets are off
|
||||
# if Synapse cannot rely on the consistency of its database.
|
||||
raise RuntimeError(
|
||||
f"Database corruption: Event {event_id} in room {d['room_id']} "
|
||||
f"from the database appears to have been modified (calculated "
|
||||
f"event id {original_ev.event_id})"
|
||||
raise DatabaseCorruptionError(
|
||||
d["room_id"], event_id, original_ev.event_id
|
||||
)
|
||||
|
||||
event_map[event_id] = original_ev
|
||||
|
||||
@@ -53,7 +53,12 @@ from synapse.storage.database import (
|
||||
from synapse.storage.databases.main.cache import CacheInvalidationWorkerStore
|
||||
from synapse.storage.databases.main.events_worker import EventsWorkerStore
|
||||
from synapse.storage.engines import Sqlite3Engine
|
||||
from synapse.storage.roommember import MemberSummary, ProfileInfo, RoomsForUser
|
||||
from synapse.storage.roommember import (
|
||||
MemberSummary,
|
||||
ProfileInfo,
|
||||
RoomsForUser,
|
||||
RoomsForUserSlidingSync,
|
||||
)
|
||||
from synapse.types import (
|
||||
JsonDict,
|
||||
PersistedEventPosition,
|
||||
@@ -1377,6 +1382,54 @@ class RoomMemberWorkerStore(EventsWorkerStore, CacheInvalidationWorkerStore):
|
||||
desc="room_forgetter_stream_pos",
|
||||
)
|
||||
|
||||
@cached(iterable=True, max_entries=10000)
|
||||
async def get_sliding_sync_rooms_for_user(
|
||||
self,
|
||||
user_id: str,
|
||||
) -> Mapping[str, RoomsForUserSlidingSync]:
|
||||
"""Get all the rooms for a user to handle a sliding sync request.
|
||||
|
||||
Ignores forgotten rooms and rooms that the user has been kicked from.
|
||||
|
||||
Returns:
|
||||
Map from room ID to membership info
|
||||
"""
|
||||
|
||||
def get_sliding_sync_rooms_for_user_txn(
|
||||
txn: LoggingTransaction,
|
||||
) -> Dict[str, RoomsForUserSlidingSync]:
|
||||
sql = """
|
||||
SELECT m.room_id, m.sender, m.membership, m.membership_event_id,
|
||||
r.room_version,
|
||||
m.event_instance_name, m.event_stream_ordering,
|
||||
COALESCE(j.room_type, m.room_type),
|
||||
COALESCE(j.is_encrypted, m.is_encrypted)
|
||||
FROM sliding_sync_membership_snapshots AS m
|
||||
INNER JOIN rooms AS r USING (room_id)
|
||||
LEFT JOIN sliding_sync_joined_rooms AS j ON (j.room_id = m.room_id AND m.membership = 'join')
|
||||
WHERE user_id = ?
|
||||
AND m.forgotten = 0
|
||||
"""
|
||||
txn.execute(sql, (user_id,))
|
||||
return {
|
||||
row[0]: RoomsForUserSlidingSync(
|
||||
room_id=row[0],
|
||||
sender=row[1],
|
||||
membership=row[2],
|
||||
event_id=row[3],
|
||||
room_version_id=row[4],
|
||||
event_pos=PersistedEventPosition(row[5], row[6]),
|
||||
room_type=row[7],
|
||||
is_encrypted=row[8],
|
||||
)
|
||||
for row in txn
|
||||
}
|
||||
|
||||
return await self.db_pool.runInteraction(
|
||||
"get_sliding_sync_rooms_for_user",
|
||||
get_sliding_sync_rooms_for_user_txn,
|
||||
)
|
||||
|
||||
|
||||
class RoomMemberBackgroundUpdateStore(SQLBaseStore):
|
||||
def __init__(
|
||||
|
||||
@@ -39,6 +39,19 @@ class RoomsForUser:
|
||||
room_version_id: str
|
||||
|
||||
|
||||
@attr.s(slots=True, frozen=True, weakref_slot=False, auto_attribs=True)
|
||||
class RoomsForUserSlidingSync:
|
||||
room_id: str
|
||||
sender: Optional[str]
|
||||
membership: str
|
||||
event_id: Optional[str]
|
||||
event_pos: PersistedEventPosition
|
||||
room_version_id: str
|
||||
|
||||
room_type: Optional[str]
|
||||
is_encrypted: bool
|
||||
|
||||
|
||||
@attr.s(slots=True, frozen=True, weakref_slot=False, auto_attribs=True)
|
||||
class GetRoomsForUserWithStreamOrdering:
|
||||
room_id: str
|
||||
|
||||
+3
-1
@@ -27,7 +27,9 @@ from synapse.types import ISynapseReactor
|
||||
try:
|
||||
from twisted.internet.epollreactor import EPollReactor as Reactor
|
||||
except ImportError:
|
||||
from twisted.internet.pollreactor import PollReactor as Reactor # type: ignore[assignment]
|
||||
from twisted.internet.pollreactor import ( # type: ignore[assignment]
|
||||
PollReactor as Reactor,
|
||||
)
|
||||
from twisted.internet.main import installReactor
|
||||
|
||||
|
||||
|
||||
@@ -550,7 +550,7 @@ class MSC3861OAuthDelegation(HomeserverTestCase):
|
||||
access_token="mockAccessToken",
|
||||
)
|
||||
|
||||
self.assertEqual(channel.code, HTTPStatus.NOT_IMPLEMENTED, channel.json_body)
|
||||
self.assertEqual(channel.code, HTTPStatus.UNAUTHORIZED, channel.json_body)
|
||||
|
||||
def expect_unauthorized(
|
||||
self, method: str, path: str, content: Union[bytes, str, JsonDict] = ""
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#
|
||||
import logging
|
||||
|
||||
from parameterized import parameterized
|
||||
from parameterized import parameterized, parameterized_class
|
||||
|
||||
from twisted.test.proto_helpers import MemoryReactor
|
||||
|
||||
@@ -28,6 +28,18 @@ from tests.rest.client.sliding_sync.test_sliding_sync import SlidingSyncBase
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# FIXME: This can be removed once we bump `SCHEMA_COMPAT_VERSION` and run the
|
||||
# foreground update for
|
||||
# `sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` (tracked by
|
||||
# https://github.com/element-hq/synapse/issues/17623)
|
||||
@parameterized_class(
|
||||
("use_new_tables",),
|
||||
[
|
||||
(True,),
|
||||
(False,),
|
||||
],
|
||||
class_name_func=lambda cls, num, params_dict: f"{cls.__name__}_{'new' if params_dict['use_new_tables'] else 'fallback'}",
|
||||
)
|
||||
class SlidingSyncConnectionTrackingTestCase(SlidingSyncBase):
|
||||
"""
|
||||
Test connection tracking in the Sliding Sync API.
|
||||
@@ -44,6 +56,8 @@ class SlidingSyncConnectionTrackingTestCase(SlidingSyncBase):
|
||||
self.store = hs.get_datastores().main
|
||||
self.storage_controllers = hs.get_storage_controllers()
|
||||
|
||||
super().prepare(reactor, clock, hs)
|
||||
|
||||
def test_rooms_required_state_incremental_sync_LIVE(self) -> None:
|
||||
"""Test that we only get state updates in incremental sync for rooms
|
||||
we've already seen (LIVE).
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#
|
||||
import logging
|
||||
|
||||
from parameterized import parameterized_class
|
||||
|
||||
from twisted.test.proto_helpers import MemoryReactor
|
||||
|
||||
import synapse.rest.admin
|
||||
@@ -28,6 +30,18 @@ from tests.server import TimedOutException
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# FIXME: This can be removed once we bump `SCHEMA_COMPAT_VERSION` and run the
|
||||
# foreground update for
|
||||
# `sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` (tracked by
|
||||
# https://github.com/element-hq/synapse/issues/17623)
|
||||
@parameterized_class(
|
||||
("use_new_tables",),
|
||||
[
|
||||
(True,),
|
||||
(False,),
|
||||
],
|
||||
class_name_func=lambda cls, num, params_dict: f"{cls.__name__}_{'new' if params_dict['use_new_tables'] else 'fallback'}",
|
||||
)
|
||||
class SlidingSyncAccountDataExtensionTestCase(SlidingSyncBase):
|
||||
"""Tests for the account_data sliding sync extension"""
|
||||
|
||||
@@ -43,6 +57,8 @@ class SlidingSyncAccountDataExtensionTestCase(SlidingSyncBase):
|
||||
self.store = hs.get_datastores().main
|
||||
self.account_data_handler = hs.get_account_data_handler()
|
||||
|
||||
super().prepare(reactor, clock, hs)
|
||||
|
||||
def test_no_data_initial_sync(self) -> None:
|
||||
"""
|
||||
Test that enabling the account_data extension works during an intitial sync,
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#
|
||||
import logging
|
||||
|
||||
from parameterized import parameterized_class
|
||||
|
||||
from twisted.test.proto_helpers import MemoryReactor
|
||||
|
||||
import synapse.rest.admin
|
||||
@@ -27,6 +29,18 @@ from tests.server import TimedOutException
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# FIXME: This can be removed once we bump `SCHEMA_COMPAT_VERSION` and run the
|
||||
# foreground update for
|
||||
# `sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` (tracked by
|
||||
# https://github.com/element-hq/synapse/issues/17623)
|
||||
@parameterized_class(
|
||||
("use_new_tables",),
|
||||
[
|
||||
(True,),
|
||||
(False,),
|
||||
],
|
||||
class_name_func=lambda cls, num, params_dict: f"{cls.__name__}_{'new' if params_dict['use_new_tables'] else 'fallback'}",
|
||||
)
|
||||
class SlidingSyncE2eeExtensionTestCase(SlidingSyncBase):
|
||||
"""Tests for the e2ee sliding sync extension"""
|
||||
|
||||
@@ -42,6 +56,8 @@ class SlidingSyncE2eeExtensionTestCase(SlidingSyncBase):
|
||||
self.store = hs.get_datastores().main
|
||||
self.e2e_keys_handler = hs.get_e2e_keys_handler()
|
||||
|
||||
super().prepare(reactor, clock, hs)
|
||||
|
||||
def test_no_data_initial_sync(self) -> None:
|
||||
"""
|
||||
Test that enabling e2ee extension works during an intitial sync, even if there
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#
|
||||
import logging
|
||||
|
||||
from parameterized import parameterized_class
|
||||
|
||||
from twisted.test.proto_helpers import MemoryReactor
|
||||
|
||||
import synapse.rest.admin
|
||||
@@ -28,6 +30,18 @@ from tests.server import TimedOutException
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# FIXME: This can be removed once we bump `SCHEMA_COMPAT_VERSION` and run the
|
||||
# foreground update for
|
||||
# `sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` (tracked by
|
||||
# https://github.com/element-hq/synapse/issues/17623)
|
||||
@parameterized_class(
|
||||
("use_new_tables",),
|
||||
[
|
||||
(True,),
|
||||
(False,),
|
||||
],
|
||||
class_name_func=lambda cls, num, params_dict: f"{cls.__name__}_{'new' if params_dict['use_new_tables'] else 'fallback'}",
|
||||
)
|
||||
class SlidingSyncReceiptsExtensionTestCase(SlidingSyncBase):
|
||||
"""Tests for the receipts sliding sync extension"""
|
||||
|
||||
@@ -42,6 +56,8 @@ class SlidingSyncReceiptsExtensionTestCase(SlidingSyncBase):
|
||||
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
|
||||
self.store = hs.get_datastores().main
|
||||
|
||||
super().prepare(reactor, clock, hs)
|
||||
|
||||
def test_no_data_initial_sync(self) -> None:
|
||||
"""
|
||||
Test that enabling the receipts extension works during an intitial sync,
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from parameterized import parameterized_class
|
||||
|
||||
from twisted.test.proto_helpers import MemoryReactor
|
||||
|
||||
import synapse.rest.admin
|
||||
@@ -28,6 +30,18 @@ from tests.server import TimedOutException
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# FIXME: This can be removed once we bump `SCHEMA_COMPAT_VERSION` and run the
|
||||
# foreground update for
|
||||
# `sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` (tracked by
|
||||
# https://github.com/element-hq/synapse/issues/17623)
|
||||
@parameterized_class(
|
||||
("use_new_tables",),
|
||||
[
|
||||
(True,),
|
||||
(False,),
|
||||
],
|
||||
class_name_func=lambda cls, num, params_dict: f"{cls.__name__}_{'new' if params_dict['use_new_tables'] else 'fallback'}",
|
||||
)
|
||||
class SlidingSyncToDeviceExtensionTestCase(SlidingSyncBase):
|
||||
"""Tests for the to-device sliding sync extension"""
|
||||
|
||||
@@ -40,6 +54,7 @@ class SlidingSyncToDeviceExtensionTestCase(SlidingSyncBase):
|
||||
|
||||
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
|
||||
self.store = hs.get_datastores().main
|
||||
super().prepare(reactor, clock, hs)
|
||||
|
||||
def _assert_to_device_response(
|
||||
self, response_body: JsonDict, expected_messages: List[JsonDict]
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#
|
||||
import logging
|
||||
|
||||
from parameterized import parameterized_class
|
||||
|
||||
from twisted.test.proto_helpers import MemoryReactor
|
||||
|
||||
import synapse.rest.admin
|
||||
@@ -28,6 +30,18 @@ from tests.server import TimedOutException
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# FIXME: This can be removed once we bump `SCHEMA_COMPAT_VERSION` and run the
|
||||
# foreground update for
|
||||
# `sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` (tracked by
|
||||
# https://github.com/element-hq/synapse/issues/17623)
|
||||
@parameterized_class(
|
||||
("use_new_tables",),
|
||||
[
|
||||
(True,),
|
||||
(False,),
|
||||
],
|
||||
class_name_func=lambda cls, num, params_dict: f"{cls.__name__}_{'new' if params_dict['use_new_tables'] else 'fallback'}",
|
||||
)
|
||||
class SlidingSyncTypingExtensionTestCase(SlidingSyncBase):
|
||||
"""Tests for the typing notification sliding sync extension"""
|
||||
|
||||
@@ -41,6 +55,8 @@ class SlidingSyncTypingExtensionTestCase(SlidingSyncBase):
|
||||
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
|
||||
self.store = hs.get_datastores().main
|
||||
|
||||
super().prepare(reactor, clock, hs)
|
||||
|
||||
def test_no_data_initial_sync(self) -> None:
|
||||
"""
|
||||
Test that enabling the typing extension works during an intitial sync,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
import logging
|
||||
from typing import Literal
|
||||
|
||||
from parameterized import parameterized
|
||||
from parameterized import parameterized, parameterized_class
|
||||
from typing_extensions import assert_never
|
||||
|
||||
from twisted.test.proto_helpers import MemoryReactor
|
||||
@@ -30,6 +30,18 @@ from tests.rest.client.sliding_sync.test_sliding_sync import SlidingSyncBase
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# FIXME: This can be removed once we bump `SCHEMA_COMPAT_VERSION` and run the
|
||||
# foreground update for
|
||||
# `sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` (tracked by
|
||||
# https://github.com/element-hq/synapse/issues/17623)
|
||||
@parameterized_class(
|
||||
("use_new_tables",),
|
||||
[
|
||||
(True,),
|
||||
(False,),
|
||||
],
|
||||
class_name_func=lambda cls, num, params_dict: f"{cls.__name__}_{'new' if params_dict['use_new_tables'] else 'fallback'}",
|
||||
)
|
||||
class SlidingSyncExtensionsTestCase(SlidingSyncBase):
|
||||
"""
|
||||
Test general extensions behavior in the Sliding Sync API. Each extension has their
|
||||
@@ -49,6 +61,8 @@ class SlidingSyncExtensionsTestCase(SlidingSyncBase):
|
||||
self.storage_controllers = hs.get_storage_controllers()
|
||||
self.account_data_handler = hs.get_account_data_handler()
|
||||
|
||||
super().prepare(reactor, clock, hs)
|
||||
|
||||
# Any extensions that use `lists`/`rooms` should be tested here
|
||||
@parameterized.expand([("account_data",), ("receipts",), ("typing",)])
|
||||
def test_extensions_lists_rooms_relevant_rooms(
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
import logging
|
||||
from http import HTTPStatus
|
||||
|
||||
from parameterized import parameterized_class
|
||||
|
||||
from twisted.test.proto_helpers import MemoryReactor
|
||||
|
||||
import synapse.rest.admin
|
||||
@@ -27,6 +29,18 @@ from tests.rest.client.sliding_sync.test_sliding_sync import SlidingSyncBase
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# FIXME: This can be removed once we bump `SCHEMA_COMPAT_VERSION` and run the
|
||||
# foreground update for
|
||||
# `sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` (tracked by
|
||||
# https://github.com/element-hq/synapse/issues/17623)
|
||||
@parameterized_class(
|
||||
("use_new_tables",),
|
||||
[
|
||||
(True,),
|
||||
(False,),
|
||||
],
|
||||
class_name_func=lambda cls, num, params_dict: f"{cls.__name__}_{'new' if params_dict['use_new_tables'] else 'fallback'}",
|
||||
)
|
||||
class SlidingSyncRoomSubscriptionsTestCase(SlidingSyncBase):
|
||||
"""
|
||||
Test `room_subscriptions` in the Sliding Sync API.
|
||||
@@ -43,6 +57,8 @@ class SlidingSyncRoomSubscriptionsTestCase(SlidingSyncBase):
|
||||
self.store = hs.get_datastores().main
|
||||
self.storage_controllers = hs.get_storage_controllers()
|
||||
|
||||
super().prepare(reactor, clock, hs)
|
||||
|
||||
def test_room_subscriptions_with_join_membership(self) -> None:
|
||||
"""
|
||||
Test `room_subscriptions` with a joined room should give us timeline and current
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#
|
||||
import logging
|
||||
|
||||
from parameterized import parameterized_class
|
||||
|
||||
from twisted.test.proto_helpers import MemoryReactor
|
||||
|
||||
import synapse.rest.admin
|
||||
@@ -27,6 +29,18 @@ from tests.rest.client.sliding_sync.test_sliding_sync import SlidingSyncBase
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# FIXME: This can be removed once we bump `SCHEMA_COMPAT_VERSION` and run the
|
||||
# foreground update for
|
||||
# `sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` (tracked by
|
||||
# https://github.com/element-hq/synapse/issues/17623)
|
||||
@parameterized_class(
|
||||
("use_new_tables",),
|
||||
[
|
||||
(True,),
|
||||
(False,),
|
||||
],
|
||||
class_name_func=lambda cls, num, params_dict: f"{cls.__name__}_{'new' if params_dict['use_new_tables'] else 'fallback'}",
|
||||
)
|
||||
class SlidingSyncRoomsInvitesTestCase(SlidingSyncBase):
|
||||
"""
|
||||
Test to make sure the `rooms` response looks good for invites in the Sliding Sync API.
|
||||
@@ -49,6 +63,8 @@ class SlidingSyncRoomsInvitesTestCase(SlidingSyncBase):
|
||||
self.store = hs.get_datastores().main
|
||||
self.storage_controllers = hs.get_storage_controllers()
|
||||
|
||||
super().prepare(reactor, clock, hs)
|
||||
|
||||
def test_rooms_invite_shared_history_initial_sync(self) -> None:
|
||||
"""
|
||||
Test that `rooms` we are invited to have some stripped `invite_state` during an
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#
|
||||
import logging
|
||||
|
||||
from parameterized import parameterized_class
|
||||
|
||||
from twisted.test.proto_helpers import MemoryReactor
|
||||
|
||||
import synapse.rest.admin
|
||||
@@ -28,6 +30,18 @@ from tests.test_utils.event_injection import create_event
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# FIXME: This can be removed once we bump `SCHEMA_COMPAT_VERSION` and run the
|
||||
# foreground update for
|
||||
# `sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` (tracked by
|
||||
# https://github.com/element-hq/synapse/issues/17623)
|
||||
@parameterized_class(
|
||||
("use_new_tables",),
|
||||
[
|
||||
(True,),
|
||||
(False,),
|
||||
],
|
||||
class_name_func=lambda cls, num, params_dict: f"{cls.__name__}_{'new' if params_dict['use_new_tables'] else 'fallback'}",
|
||||
)
|
||||
class SlidingSyncRoomsMetaTestCase(SlidingSyncBase):
|
||||
"""
|
||||
Test rooms meta info like name, avatar, joined_count, invited_count, is_dm,
|
||||
@@ -49,6 +63,8 @@ class SlidingSyncRoomsMetaTestCase(SlidingSyncBase):
|
||||
assert persistence is not None
|
||||
self.persistence = persistence
|
||||
|
||||
super().prepare(reactor, clock, hs)
|
||||
|
||||
def test_rooms_meta_when_joined(self) -> None:
|
||||
"""
|
||||
Test that the `rooms` `name` and `avatar` are included in the response and
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#
|
||||
import logging
|
||||
|
||||
from parameterized import parameterized
|
||||
from parameterized import parameterized, parameterized_class
|
||||
|
||||
from twisted.test.proto_helpers import MemoryReactor
|
||||
|
||||
@@ -30,6 +30,18 @@ from tests.test_utils.event_injection import mark_event_as_partial_state
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# FIXME: This can be removed once we bump `SCHEMA_COMPAT_VERSION` and run the
|
||||
# foreground update for
|
||||
# `sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` (tracked by
|
||||
# https://github.com/element-hq/synapse/issues/17623)
|
||||
@parameterized_class(
|
||||
("use_new_tables",),
|
||||
[
|
||||
(True,),
|
||||
(False,),
|
||||
],
|
||||
class_name_func=lambda cls, num, params_dict: f"{cls.__name__}_{'new' if params_dict['use_new_tables'] else 'fallback'}",
|
||||
)
|
||||
class SlidingSyncRoomsRequiredStateTestCase(SlidingSyncBase):
|
||||
"""
|
||||
Test `rooms.required_state` in the Sliding Sync API.
|
||||
@@ -46,6 +58,8 @@ class SlidingSyncRoomsRequiredStateTestCase(SlidingSyncBase):
|
||||
self.store = hs.get_datastores().main
|
||||
self.storage_controllers = hs.get_storage_controllers()
|
||||
|
||||
super().prepare(reactor, clock, hs)
|
||||
|
||||
def test_rooms_no_required_state(self) -> None:
|
||||
"""
|
||||
Empty `rooms.required_state` should not return any state events in the room
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
from parameterized import parameterized_class
|
||||
|
||||
from twisted.test.proto_helpers import MemoryReactor
|
||||
|
||||
import synapse.rest.admin
|
||||
@@ -28,6 +30,18 @@ from tests.rest.client.sliding_sync.test_sliding_sync import SlidingSyncBase
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# FIXME: This can be removed once we bump `SCHEMA_COMPAT_VERSION` and run the
|
||||
# foreground update for
|
||||
# `sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` (tracked by
|
||||
# https://github.com/element-hq/synapse/issues/17623)
|
||||
@parameterized_class(
|
||||
("use_new_tables",),
|
||||
[
|
||||
(True,),
|
||||
(False,),
|
||||
],
|
||||
class_name_func=lambda cls, num, params_dict: f"{cls.__name__}_{'new' if params_dict['use_new_tables'] else 'fallback'}",
|
||||
)
|
||||
class SlidingSyncRoomsTimelineTestCase(SlidingSyncBase):
|
||||
"""
|
||||
Test `rooms.timeline` in the Sliding Sync API.
|
||||
@@ -44,6 +58,8 @@ class SlidingSyncRoomsTimelineTestCase(SlidingSyncBase):
|
||||
self.store = hs.get_datastores().main
|
||||
self.storage_controllers = hs.get_storage_controllers()
|
||||
|
||||
super().prepare(reactor, clock, hs)
|
||||
|
||||
def _assertListEqual(
|
||||
self,
|
||||
actual_items: StrSequence,
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
#
|
||||
import logging
|
||||
from typing import Any, Dict, Iterable, List, Literal, Optional, Tuple
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from parameterized import parameterized_class
|
||||
from typing_extensions import assert_never
|
||||
|
||||
from twisted.test.proto_helpers import MemoryReactor
|
||||
@@ -47,8 +49,25 @@ logger = logging.getLogger(__name__)
|
||||
class SlidingSyncBase(unittest.HomeserverTestCase):
|
||||
"""Base class for sliding sync test cases"""
|
||||
|
||||
# Flag as to whether to use the new sliding sync tables or not
|
||||
#
|
||||
# FIXME: This can be removed once we bump `SCHEMA_COMPAT_VERSION` and run the
|
||||
# foreground update for
|
||||
# `sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` (tracked by
|
||||
# https://github.com/element-hq/synapse/issues/17623)
|
||||
use_new_tables: bool = True
|
||||
|
||||
sync_endpoint = "/_matrix/client/unstable/org.matrix.simplified_msc3575/sync"
|
||||
|
||||
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
|
||||
# FIXME: This can be removed once we bump `SCHEMA_COMPAT_VERSION` and run the
|
||||
# foreground update for
|
||||
# `sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` (tracked by
|
||||
# https://github.com/element-hq/synapse/issues/17623)
|
||||
hs.get_datastores().main.have_finished_sliding_sync_background_jobs = AsyncMock( # type: ignore[method-assign]
|
||||
return_value=self.use_new_tables
|
||||
)
|
||||
|
||||
def default_config(self) -> JsonDict:
|
||||
config = super().default_config()
|
||||
# Enable sliding sync
|
||||
@@ -203,6 +222,18 @@ class SlidingSyncBase(unittest.HomeserverTestCase):
|
||||
)
|
||||
|
||||
|
||||
# FIXME: This can be removed once we bump `SCHEMA_COMPAT_VERSION` and run the
|
||||
# foreground update for
|
||||
# `sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` (tracked by
|
||||
# https://github.com/element-hq/synapse/issues/17623)
|
||||
@parameterized_class(
|
||||
("use_new_tables",),
|
||||
[
|
||||
(True,),
|
||||
(False,),
|
||||
],
|
||||
class_name_func=lambda cls, num, params_dict: f"{cls.__name__}_{'new' if params_dict['use_new_tables'] else 'fallback'}",
|
||||
)
|
||||
class SlidingSyncTestCase(SlidingSyncBase):
|
||||
"""
|
||||
Tests regarding MSC3575 Sliding Sync `/sync` endpoint.
|
||||
@@ -226,6 +257,8 @@ class SlidingSyncTestCase(SlidingSyncBase):
|
||||
self.storage_controllers = hs.get_storage_controllers()
|
||||
self.account_data_handler = hs.get_account_data_handler()
|
||||
|
||||
super().prepare(reactor, clock, hs)
|
||||
|
||||
def _add_new_dm_to_global_account_data(
|
||||
self, source_user_id: str, target_user_id: str, target_room_id: str
|
||||
) -> None:
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from http import HTTPStatus
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from synapse.rest.client import auth_issuer
|
||||
|
||||
@@ -50,10 +51,27 @@ class AuthIssuerTestCase(HomeserverTestCase):
|
||||
}
|
||||
)
|
||||
def test_returns_issuer_when_oidc_enabled(self) -> None:
|
||||
# Make an unauthenticated request for the discovery info.
|
||||
# Patch the HTTP client to return the issuer metadata
|
||||
req_mock = AsyncMock(return_value={"issuer": ISSUER})
|
||||
self.hs.get_proxied_http_client().get_json = req_mock # type: ignore[method-assign]
|
||||
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
"/_matrix/client/unstable/org.matrix.msc2965/auth_issuer",
|
||||
)
|
||||
|
||||
self.assertEqual(channel.code, HTTPStatus.OK)
|
||||
self.assertEqual(channel.json_body, {"issuer": ISSUER})
|
||||
|
||||
req_mock.assert_called_with("https://account.example.com/.well-known/openid-configuration")
|
||||
req_mock.reset_mock()
|
||||
|
||||
# Second call it should use the cached value
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
"/_matrix/client/unstable/org.matrix.msc2965/auth_issuer",
|
||||
)
|
||||
|
||||
self.assertEqual(channel.code, HTTPStatus.OK)
|
||||
self.assertEqual(channel.json_body, {"issuer": ISSUER})
|
||||
req_mock.assert_not_called()
|
||||
|
||||
@@ -315,9 +315,7 @@ class SigningKeyUploadServletTestCase(unittest.HomeserverTestCase):
|
||||
"master_key": master_key2,
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
channel.code, HTTPStatus.NOT_IMPLEMENTED, channel.json_body
|
||||
)
|
||||
self.assertEqual(channel.code, HTTPStatus.UNAUTHORIZED, channel.json_body)
|
||||
|
||||
# Pretend that MAS did UIA and allowed us to replace the master key.
|
||||
channel = self.make_request(
|
||||
@@ -349,9 +347,7 @@ class SigningKeyUploadServletTestCase(unittest.HomeserverTestCase):
|
||||
"master_key": master_key3,
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
channel.code, HTTPStatus.NOT_IMPLEMENTED, channel.json_body
|
||||
)
|
||||
self.assertEqual(channel.code, HTTPStatus.UNAUTHORIZED, channel.json_body)
|
||||
|
||||
# Pretend that MAS did UIA and allowed us to replace the master key.
|
||||
channel = self.make_request(
|
||||
@@ -376,6 +372,4 @@ class SigningKeyUploadServletTestCase(unittest.HomeserverTestCase):
|
||||
"master_key": master_key3,
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
channel.code, HTTPStatus.NOT_IMPLEMENTED, channel.json_body
|
||||
)
|
||||
self.assertEqual(channel.code, HTTPStatus.UNAUTHORIZED, channel.json_body)
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
# [This file includes modifications made by New Vector Limited]
|
||||
#
|
||||
#
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from twisted.web.resource import Resource
|
||||
|
||||
from synapse.rest.well_known import well_known_resource
|
||||
@@ -112,7 +114,6 @@ class WellKnownTests(unittest.HomeserverTestCase):
|
||||
"msc3861": {
|
||||
"enabled": True,
|
||||
"issuer": "https://issuer",
|
||||
"account_management_url": "https://my-account.issuer",
|
||||
"client_id": "id",
|
||||
"client_auth_method": "client_secret_post",
|
||||
"client_secret": "secret",
|
||||
@@ -122,18 +123,26 @@ class WellKnownTests(unittest.HomeserverTestCase):
|
||||
}
|
||||
)
|
||||
def test_client_well_known_msc3861_oauth_delegation(self) -> None:
|
||||
channel = self.make_request(
|
||||
"GET", "/.well-known/matrix/client", shorthand=False
|
||||
)
|
||||
# Patch the HTTP client to return the issuer metadata
|
||||
req_mock = AsyncMock(return_value={"issuer": "https://issuer", "account_management_uri": "https://my-account.issuer"})
|
||||
self.hs.get_proxied_http_client().get_json = req_mock # type: ignore[method-assign]
|
||||
|
||||
self.assertEqual(channel.code, 200)
|
||||
self.assertEqual(
|
||||
channel.json_body,
|
||||
{
|
||||
"m.homeserver": {"base_url": "https://homeserver/"},
|
||||
"org.matrix.msc2965.authentication": {
|
||||
"issuer": "https://issuer",
|
||||
"account": "https://my-account.issuer",
|
||||
for _ in range(2):
|
||||
channel = self.make_request(
|
||||
"GET", "/.well-known/matrix/client", shorthand=False
|
||||
)
|
||||
|
||||
self.assertEqual(channel.code, 200)
|
||||
self.assertEqual(
|
||||
channel.json_body,
|
||||
{
|
||||
"m.homeserver": {"base_url": "https://homeserver/"},
|
||||
"org.matrix.msc2965.authentication": {
|
||||
"issuer": "https://issuer",
|
||||
"account": "https://my-account.issuer",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
# It should have been called exactly once, because it gets cached
|
||||
req_mock.assert_called_once_with("https://issuer/.well-known/openid-configuration")
|
||||
|
||||
@@ -38,6 +38,7 @@ from synapse.storage.databases.main.events_bg_updates import (
|
||||
_resolve_stale_data_in_sliding_sync_joined_rooms_table,
|
||||
_resolve_stale_data_in_sliding_sync_membership_snapshots_table,
|
||||
)
|
||||
from synapse.types import create_requester
|
||||
from synapse.util import Clock
|
||||
|
||||
from tests.test_utils.event_injection import create_event
|
||||
@@ -925,6 +926,128 @@ class SlidingSyncTablesTestCase(SlidingSyncTablesTestCaseBase):
|
||||
user2_snapshot,
|
||||
)
|
||||
|
||||
@parameterized.expand(
|
||||
# Test both an insert an upsert into the
|
||||
# `sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` to exercise
|
||||
# more possibilities of things going wrong.
|
||||
[
|
||||
("insert", True),
|
||||
("upsert", False),
|
||||
]
|
||||
)
|
||||
def test_joined_room_outlier_and_deoutlier(
|
||||
self, description: str, should_insert: bool
|
||||
) -> None:
|
||||
"""
|
||||
This is a regression test.
|
||||
|
||||
This is to simulate the case where an event is first persisted as an outlier
|
||||
(like a remote invite) and then later persisted again to de-outlier it. The
|
||||
first the time, the `outlier` is persisted with one `stream_ordering` but when
|
||||
persisted again and de-outliered, it is assigned a different `stream_ordering`
|
||||
that won't end up being used. Since we call
|
||||
`_calculate_sliding_sync_table_changes()` before `_update_outliers_txn()` which
|
||||
fixes this discrepancy (always use the `stream_ordering` from the first time it
|
||||
was persisted), make sure we're not using an unreliable `stream_ordering` values
|
||||
that will cause `FOREIGN KEY constraint failed` in the
|
||||
`sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` tables.
|
||||
"""
|
||||
user1_id = self.register_user("user1", "pass")
|
||||
_user1_tok = self.login(user1_id, "pass")
|
||||
user2_id = self.register_user("user2", "pass")
|
||||
user2_tok = self.login(user2_id, "pass")
|
||||
|
||||
room_version = RoomVersions.V10
|
||||
room_id = self.helper.create_room_as(
|
||||
user2_id, tok=user2_tok, room_version=room_version.identifier
|
||||
)
|
||||
|
||||
if should_insert:
|
||||
# Clear these out so we always insert
|
||||
self.get_success(
|
||||
self.store.db_pool.simple_delete(
|
||||
table="sliding_sync_joined_rooms",
|
||||
keyvalues={"room_id": room_id},
|
||||
desc="TODO",
|
||||
)
|
||||
)
|
||||
self.get_success(
|
||||
self.store.db_pool.simple_delete(
|
||||
table="sliding_sync_membership_snapshots",
|
||||
keyvalues={"room_id": room_id},
|
||||
desc="TODO",
|
||||
)
|
||||
)
|
||||
|
||||
# Create a membership event (which triggers an insert into
|
||||
# `sliding_sync_membership_snapshots`)
|
||||
membership_event_dict = {
|
||||
"type": EventTypes.Member,
|
||||
"state_key": user1_id,
|
||||
"sender": user1_id,
|
||||
"room_id": room_id,
|
||||
"content": {EventContentFields.MEMBERSHIP: Membership.JOIN},
|
||||
}
|
||||
# Create a relevant state event (which triggers an insert into
|
||||
# `sliding_sync_joined_rooms`)
|
||||
state_event_dict = {
|
||||
"type": EventTypes.Name,
|
||||
"state_key": "",
|
||||
"sender": user2_id,
|
||||
"room_id": room_id,
|
||||
"content": {EventContentFields.ROOM_NAME: "my super room"},
|
||||
}
|
||||
event_dicts_to_persist = [
|
||||
membership_event_dict,
|
||||
state_event_dict,
|
||||
]
|
||||
|
||||
for event_dict in event_dicts_to_persist:
|
||||
events_to_persist = []
|
||||
|
||||
# Create the events as an outliers
|
||||
(
|
||||
event,
|
||||
unpersisted_context,
|
||||
) = self.get_success(
|
||||
self.hs.get_event_creation_handler().create_event(
|
||||
requester=create_requester(user1_id),
|
||||
event_dict=event_dict,
|
||||
outlier=True,
|
||||
)
|
||||
)
|
||||
# FIXME: Should we use an `EventContext.for_outlier(...)` here?
|
||||
# Doesn't seem to matter for this test.
|
||||
context = self.get_success(unpersisted_context.persist(event))
|
||||
events_to_persist.append((event, context))
|
||||
|
||||
# Create the event again but as an non-outlier. This will de-outlier the event
|
||||
# when we persist it.
|
||||
(
|
||||
event,
|
||||
unpersisted_context,
|
||||
) = self.get_success(
|
||||
self.hs.get_event_creation_handler().create_event(
|
||||
requester=create_requester(user1_id),
|
||||
event_dict=event_dict,
|
||||
outlier=False,
|
||||
)
|
||||
)
|
||||
context = self.get_success(unpersisted_context.persist(event))
|
||||
events_to_persist.append((event, context))
|
||||
|
||||
persist_controller = self.hs.get_storage_controllers().persistence
|
||||
assert persist_controller is not None
|
||||
for event, context in events_to_persist:
|
||||
self.get_success(
|
||||
persist_controller.persist_event(
|
||||
event,
|
||||
context,
|
||||
)
|
||||
)
|
||||
|
||||
# We're just testing that it does not explode
|
||||
|
||||
def test_joined_room_meta_state_reset(self) -> None:
|
||||
"""
|
||||
Test that a state reset on the room name is reflected in the
|
||||
|
||||
Reference in New Issue
Block a user