mirror of
https://github.com/element-hq/synapse.git
synced 2025-12-13 01:50:46 +00:00
Compare commits
451 Commits
erik-hacke
...
erikj/chun
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb3e8d9375 | ||
|
|
26d5e8b49c | ||
|
|
aadcb6298d | ||
|
|
8446bce733 | ||
|
|
76879578ea | ||
|
|
c75f2dda46 | ||
|
|
bf599cdba1 | ||
|
|
6188512b18 | ||
|
|
384731330d | ||
|
|
9e1d3f119a | ||
|
|
f687d8fae2 | ||
|
|
ecd4931ab2 | ||
|
|
1cdd0d3b0d | ||
|
|
1810cc3f7e | ||
|
|
6c1d13a15a | ||
|
|
13dbcafb9b | ||
|
|
bcc9e7f777 | ||
|
|
9c36c150e7 | ||
|
|
cc1349c06a | ||
|
|
5b788aba90 | ||
|
|
0e61705661 | ||
|
|
17a70cf6e9 | ||
|
|
6c16a4ec1b | ||
|
|
7ea07c7305 | ||
|
|
1f69693347 | ||
|
|
c4fb15a06c | ||
|
|
36501068d8 | ||
|
|
2aff6eab6d | ||
|
|
095292304f | ||
|
|
ecc4b88bd1 | ||
|
|
46345187cc | ||
|
|
037c6db85d | ||
|
|
7a1af504d7 | ||
|
|
14ca678674 | ||
|
|
6f67163c63 | ||
|
|
bdd2ed5acf | ||
|
|
f72d5a44d5 | ||
|
|
68399fc4de | ||
|
|
91d95a1d8e | ||
|
|
8c98281b8d | ||
|
|
6abcb5d22d | ||
|
|
9bf4b2bda3 | ||
|
|
23aa70cea8 | ||
|
|
043f05a078 | ||
|
|
96f07cebda | ||
|
|
a0b3946fe2 | ||
|
|
2f7008d4eb | ||
|
|
e206b2c9ac | ||
|
|
2df8c3139a | ||
|
|
dda40fb55d | ||
|
|
3ff6f50eac | ||
|
|
82191b08f6 | ||
|
|
2c62ea2515 | ||
|
|
cd8ab9a0d8 | ||
|
|
321f02d263 | ||
|
|
1cbb8e5a33 | ||
|
|
052d08a6a5 | ||
|
|
5ad1149f38 | ||
|
|
563606b8f2 | ||
|
|
2574ea3dc8 | ||
|
|
833db2d922 | ||
|
|
9e8ab0a4f4 | ||
|
|
3601a240aa | ||
|
|
e7598b666b | ||
|
|
6e11803ed3 | ||
|
|
5aaa3189d5 | ||
|
|
0a4bca4134 | ||
|
|
e85b5a0ff7 | ||
|
|
586b66b197 | ||
|
|
35ca3e7b65 | ||
|
|
a17e901f4d | ||
|
|
5494c1d71e | ||
|
|
ad2823ee27 | ||
|
|
08bfc48abf | ||
|
|
0a078026ea | ||
|
|
cb2a2ad791 | ||
|
|
08a14b32ae | ||
|
|
82c2a52987 | ||
|
|
7b36d06a69 | ||
|
|
669400e22f | ||
|
|
b5b2d5d64b | ||
|
|
3b2def6c7a | ||
|
|
a5e2941aad | ||
|
|
8aeb529262 | ||
|
|
8810685df9 | ||
|
|
d5dca9a04f | ||
|
|
9ea219c514 | ||
|
|
d14d7b8fdc | ||
|
|
7cfa8a87a1 | ||
|
|
7948ecf234 | ||
|
|
020377a550 | ||
|
|
13a8dfba0d | ||
|
|
c435b0b441 | ||
|
|
fb2806b186 | ||
|
|
413482f578 | ||
|
|
4aac88928f | ||
|
|
6e1cb54a05 | ||
|
|
6d6e7288fe | ||
|
|
d689e0dba1 | ||
|
|
dfa70adc33 | ||
|
|
933bf2dd35 | ||
|
|
d9fe2b2d9d | ||
|
|
45b55e23d3 | ||
|
|
dcc235b47d | ||
|
|
73cbdef5f7 | ||
|
|
aafb0f6b0d | ||
|
|
b932b4ea25 | ||
|
|
644aac5f73 | ||
|
|
08462620bf | ||
|
|
ef466b3a13 | ||
|
|
861f8a9b21 | ||
|
|
2725223f08 | ||
|
|
ab5e888927 | ||
|
|
f3d9dca975 | ||
|
|
0a325e5385 | ||
|
|
b725e128f8 | ||
|
|
6d9dc67139 | ||
|
|
ed3125b0a1 | ||
|
|
67af392712 | ||
|
|
011e1f4010 | ||
|
|
26305788fe | ||
|
|
d10707c810 | ||
|
|
fa30ac38cc | ||
|
|
8b1c856d81 | ||
|
|
6958459b50 | ||
|
|
88d3405332 | ||
|
|
d43d480d86 | ||
|
|
a2da6de40e | ||
|
|
450f500d0c | ||
|
|
82b0361f02 | ||
|
|
1b1b47aec6 | ||
|
|
fed62e21ad | ||
|
|
f8a1e76d64 | ||
|
|
0504d809fd | ||
|
|
12fd6d7688 | ||
|
|
a638649254 | ||
|
|
d4e4a7344f | ||
|
|
c771c124d5 | ||
|
|
3369354b56 | ||
|
|
3b505a80dc | ||
|
|
943f1029d6 | ||
|
|
f7906203f6 | ||
|
|
ae53c71d90 | ||
|
|
616da9eb1d | ||
|
|
c46367d0d7 | ||
|
|
85b8acdeb4 | ||
|
|
3c099219e0 | ||
|
|
680530cc7f | ||
|
|
43e6e82c4d | ||
|
|
dc8930ea9e | ||
|
|
c945af8799 | ||
|
|
be11a02c4f | ||
|
|
a2204cc9cc | ||
|
|
31c2502ca8 | ||
|
|
8030a825c8 | ||
|
|
c92a8aa578 | ||
|
|
05ac15ae82 | ||
|
|
5f27ed75ad | ||
|
|
37dbee6490 | ||
|
|
47815edcfa | ||
|
|
589ecc5b58 | ||
|
|
e71fb118f4 | ||
|
|
aea80a0118 | ||
|
|
f077e97914 | ||
|
|
8cbbfd16fb | ||
|
|
977765bde2 | ||
|
|
16f41237f0 | ||
|
|
c25d7ba12e | ||
|
|
6406b70aeb | ||
|
|
23e2dfe940 | ||
|
|
bd8d0cfab1 | ||
|
|
db18d854cd | ||
|
|
318711e139 | ||
|
|
7b411007e6 | ||
|
|
6b49628e3b | ||
|
|
217bc53c98 | ||
|
|
645cb4bf06 | ||
|
|
09f570b935 | ||
|
|
9589a1925e | ||
|
|
49e5a613f1 | ||
|
|
b8700dd7d0 | ||
|
|
c6f730282c | ||
|
|
09b29f9c4a | ||
|
|
4d298506dd | ||
|
|
8460e48d06 | ||
|
|
18e144fe08 | ||
|
|
bfe1f73855 | ||
|
|
5adb75bcba | ||
|
|
a5c98dda48 | ||
|
|
d26bec8a43 | ||
|
|
fcf55f2255 | ||
|
|
7ce98804ff | ||
|
|
cddf91c8b9 | ||
|
|
9896dab8f6 | ||
|
|
1e5280b7d0 | ||
|
|
75552d2148 | ||
|
|
294e9a0c9b | ||
|
|
46df23f581 | ||
|
|
52281e4c54 | ||
|
|
7e8726b8fb | ||
|
|
c0e08dc45b | ||
|
|
0461ef01b7 | ||
|
|
e2accd7f1d | ||
|
|
e5ab9cd24b | ||
|
|
60590211c1 | ||
|
|
c4af4c24ca | ||
|
|
05e0a2462c | ||
|
|
7dd13415db | ||
|
|
27cf170558 | ||
|
|
1aeb5e28a9 | ||
|
|
23ec51c94c | ||
|
|
d5377eba55 | ||
|
|
d11b8b6b65 | ||
|
|
8ff8ab3bce | ||
|
|
6c957e26f0 | ||
|
|
696f532453 | ||
|
|
3e6d306e94 | ||
|
|
274b8c6025 | ||
|
|
06c0d0ed08 | ||
|
|
bf98fa0864 | ||
|
|
678e649b78 | ||
|
|
0b7dfbb194 | ||
|
|
88868b2839 | ||
|
|
5addeaa02c | ||
|
|
6d8ec3462d | ||
|
|
95b6912045 | ||
|
|
966686c845 | ||
|
|
093d8c415a | ||
|
|
0ba609dc6f | ||
|
|
2117f84323 | ||
|
|
a7fe62f0cb | ||
|
|
2e7a94c36b | ||
|
|
a2aaa9cb3c | ||
|
|
d72faf2fad | ||
|
|
a0501ac57e | ||
|
|
0a3b51c420 | ||
|
|
31c7c29d43 | ||
|
|
902673e356 | ||
|
|
53a5fdf312 | ||
|
|
1dfd650348 | ||
|
|
9a779c2ddb | ||
|
|
a41117c63b | ||
|
|
32015e1109 | ||
|
|
3a42aed9a1 | ||
|
|
5a0be97ab2 | ||
|
|
415c6b672e | ||
|
|
4e9bdeba57 | ||
|
|
be31adb036 | ||
|
|
11607006d9 | ||
|
|
46beeb9a30 | ||
|
|
f22e7cda2c | ||
|
|
a8d8bf92e0 | ||
|
|
e482f8cd85 | ||
|
|
4f2e898c29 | ||
|
|
d4c14e1438 | ||
|
|
9f21de6a01 | ||
|
|
da602419b2 | ||
|
|
8ae7096958 | ||
|
|
562532dd2d | ||
|
|
5c2214f4c7 | ||
|
|
2414178ed6 | ||
|
|
40d1bbd257 | ||
|
|
8e6bd0e324 | ||
|
|
8570bb84cc | ||
|
|
ca7211104e | ||
|
|
d5eee5d601 | ||
|
|
d858f3bd4e | ||
|
|
33f469ba19 | ||
|
|
dd1a832419 | ||
|
|
d0857702e8 | ||
|
|
5917562b60 | ||
|
|
6495dbb326 | ||
|
|
2ad3fc36e6 | ||
|
|
cead75fae3 | ||
|
|
576b71dd3d | ||
|
|
99a54bf2af | ||
|
|
63ae5cbf34 | ||
|
|
fdb6849b81 | ||
|
|
66aa32ede2 | ||
|
|
6e005d1382 | ||
|
|
01e8a52825 | ||
|
|
0c9db26260 | ||
|
|
950a32eb47 | ||
|
|
bc2017a594 | ||
|
|
683149c1f9 | ||
|
|
7b908aeec4 | ||
|
|
3b0e431c82 | ||
|
|
db75c86e84 | ||
|
|
2fd96727b1 | ||
|
|
b8ee12b978 | ||
|
|
049b0b5af2 | ||
|
|
d1d54d6088 | ||
|
|
ac5f2f4d86 | ||
|
|
af3cc50511 | ||
|
|
dbf6f28d64 | ||
|
|
7767a9fc0e | ||
|
|
aab2e4da60 | ||
|
|
1315d374cc | ||
|
|
9e2601f830 | ||
|
|
122593265b | ||
|
|
e9143b6593 | ||
|
|
adaf3ec87f | ||
|
|
006e18b6bb | ||
|
|
42c89c8215 | ||
|
|
d82b6ea9e6 | ||
|
|
4f2f5171b7 | ||
|
|
94f4d7f49e | ||
|
|
57b58e2174 | ||
|
|
cdb4647a80 | ||
|
|
a376d8f761 | ||
|
|
4f5694e2ce | ||
|
|
9558236728 | ||
|
|
453adf00b6 | ||
|
|
fc149b4eeb | ||
|
|
6146332387 | ||
|
|
d2737c1fae | ||
|
|
2a13af23bc | ||
|
|
3d1ae61399 | ||
|
|
9d2c1b8429 | ||
|
|
13843f771e | ||
|
|
41d4b07a53 | ||
|
|
05ba7e3a44 | ||
|
|
53849ea9d3 | ||
|
|
268e40341b | ||
|
|
9c3da24561 | ||
|
|
53494c34df | ||
|
|
6493b22b42 | ||
|
|
6e10eed28e | ||
|
|
605defb9e4 | ||
|
|
9255a6cb17 | ||
|
|
d842ed14f4 | ||
|
|
31c8be956f | ||
|
|
28dd536e80 | ||
|
|
8721580303 | ||
|
|
dbf76fd4b9 | ||
|
|
d78ada3166 | ||
|
|
0ced8b5b47 | ||
|
|
7ec8e798b4 | ||
|
|
fb6015d0a6 | ||
|
|
a5ad88913c | ||
|
|
617bf40924 | ||
|
|
22881b3d69 | ||
|
|
ba3166743c | ||
|
|
e3a373f002 | ||
|
|
48c01ae851 | ||
|
|
6ab3b9c743 | ||
|
|
1bb83d5d41 | ||
|
|
13a2beabca | ||
|
|
2c3e995f38 | ||
|
|
8e8b06715f | ||
|
|
08b29d4574 | ||
|
|
77ebef9d43 | ||
|
|
9b9c38373c | ||
|
|
286e20f2bc | ||
|
|
1ea904b9f0 | ||
|
|
dc875d2712 | ||
|
|
8dc4a6144b | ||
|
|
d06a9ea5f7 | ||
|
|
c09a6daf09 | ||
|
|
692a3cc806 | ||
|
|
366dd893fc | ||
|
|
bdb7714d13 | ||
|
|
67dabe143d | ||
|
|
3de7d9fe99 | ||
|
|
11a67b7c9d | ||
|
|
0c280d4d99 | ||
|
|
bc381d5798 | ||
|
|
b1dfbc3c40 | ||
|
|
dacf3a50ac | ||
|
|
1f4b498b73 | ||
|
|
e585228860 | ||
|
|
9b7794262f | ||
|
|
639480e14a | ||
|
|
878995e660 | ||
|
|
a1a3c9660f | ||
|
|
512633ef44 | ||
|
|
2a3c33ff03 | ||
|
|
f63ff73c7f | ||
|
|
36c59ce669 | ||
|
|
cb9cdfecd0 | ||
|
|
1515560f5c | ||
|
|
bfc2ade9b3 | ||
|
|
c4bdbc2bd2 | ||
|
|
041b41a825 | ||
|
|
154b44c249 | ||
|
|
0d8c50df44 | ||
|
|
78a9698650 | ||
|
|
25b0ba30b1 | ||
|
|
f8d46cad3c | ||
|
|
d4b2e05852 | ||
|
|
eb53439c4a | ||
|
|
51d628d28d | ||
|
|
df77837a33 | ||
|
|
fac3f9e678 | ||
|
|
60f6014bb7 | ||
|
|
119596ab8f | ||
|
|
b78395b7fe | ||
|
|
427e6c4059 | ||
|
|
781cd8c54f | ||
|
|
9ef0b179e0 | ||
|
|
a3f9ddbede | ||
|
|
7f8eebc8ee | ||
|
|
a13b7860c6 | ||
|
|
16aeb41547 | ||
|
|
757f1b5843 | ||
|
|
5a6e54264d | ||
|
|
f44b7c022f | ||
|
|
07f1b71819 | ||
|
|
b815aa0e2d | ||
|
|
6f0b1f85f9 | ||
|
|
ca70148c05 | ||
|
|
e511979fe6 | ||
|
|
a03c382966 | ||
|
|
48e2c641b8 | ||
|
|
d8680c969b | ||
|
|
b9b668e4bb | ||
|
|
ef1f8d4be6 | ||
|
|
a0af0054ec | ||
|
|
914a59cb8c | ||
|
|
e174c46a29 | ||
|
|
b8a4dceb3c | ||
|
|
084afbb6a0 | ||
|
|
58df3a8c5d | ||
|
|
63fd148724 | ||
|
|
1ffd9cb936 | ||
|
|
107a5c9441 | ||
|
|
ee3b160a2a | ||
|
|
630573a932 | ||
|
|
f5364b47ec | ||
|
|
d8c7da5dca | ||
|
|
cf4ef60e28 | ||
|
|
cd51931b62 | ||
|
|
81010a126e | ||
|
|
8db84e9b21 | ||
|
|
e9021e16c4 | ||
|
|
f72c9c1fb6 | ||
|
|
b8ab78b82c | ||
|
|
9a87b8aaf7 | ||
|
|
84a9209ba7 | ||
|
|
53965334da | ||
|
|
a207cccb05 | ||
|
|
1ba2fe114c | ||
|
|
042757feb2 | ||
|
|
886c2d5019 | ||
|
|
f2bf0cda02 | ||
|
|
6d1e28a842 | ||
|
|
48bc22f89d | ||
|
|
d434ae3387 | ||
|
|
431476fbc4 | ||
|
|
24d162814b | ||
|
|
95e02b856b |
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Dockerfile
|
||||||
|
.travis.yml
|
||||||
|
.gitignore
|
||||||
|
demo/etc
|
||||||
|
tox.ini
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -32,6 +32,7 @@ demo/media_store.*
|
|||||||
demo/etc
|
demo/etc
|
||||||
|
|
||||||
uploads
|
uploads
|
||||||
|
cache
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
media_store/
|
media_store/
|
||||||
@@ -48,3 +49,4 @@ env/
|
|||||||
*.config
|
*.config
|
||||||
|
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.ropeproject/
|
||||||
|
|||||||
18
.travis.yml
18
.travis.yml
@@ -1,14 +1,22 @@
|
|||||||
sudo: false
|
sudo: false
|
||||||
language: python
|
language: python
|
||||||
python: 2.7
|
|
||||||
|
|
||||||
# tell travis to cache ~/.cache/pip
|
# tell travis to cache ~/.cache/pip
|
||||||
cache: pip
|
cache: pip
|
||||||
|
|
||||||
env:
|
matrix:
|
||||||
- TOX_ENV=packaging
|
include:
|
||||||
- TOX_ENV=pep8
|
- python: 2.7
|
||||||
- TOX_ENV=py27
|
env: TOX_ENV=packaging
|
||||||
|
|
||||||
|
- python: 2.7
|
||||||
|
env: TOX_ENV=pep8
|
||||||
|
|
||||||
|
- python: 2.7
|
||||||
|
env: TOX_ENV=py27
|
||||||
|
|
||||||
|
- python: 3.6
|
||||||
|
env: TOX_ENV=py36
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- pip install tox
|
- pip install tox
|
||||||
|
|||||||
@@ -60,3 +60,6 @@ Niklas Riekenbrauck <nikriek at gmail dot.com>
|
|||||||
|
|
||||||
Christoph Witzany <christoph at web.crofting.com>
|
Christoph Witzany <christoph at web.crofting.com>
|
||||||
* Add LDAP support for authentication
|
* Add LDAP support for authentication
|
||||||
|
|
||||||
|
Pierre Jaury <pierre at jaury.eu>
|
||||||
|
* Docker packaging
|
||||||
244
CHANGES.rst
244
CHANGES.rst
@@ -1,15 +1,254 @@
|
|||||||
|
Changes in <unreleased>
|
||||||
|
=======================
|
||||||
|
|
||||||
|
This release adds an index to the events table. This means that on first
|
||||||
|
startup there will be an inceased amount of IO until the index is created, and
|
||||||
|
an increase in disk usage.
|
||||||
|
|
||||||
|
|
||||||
|
Changes in synapse v0.30.0 (2018-05-24)
|
||||||
|
==========================================
|
||||||
|
|
||||||
|
'Server Notices' are a new feature introduced in Synapse 0.30. They provide a
|
||||||
|
channel whereby server administrators can send messages to users on the server.
|
||||||
|
|
||||||
|
They are used as part of communication of the server policies (see ``docs/consent_tracking.md``),
|
||||||
|
however the intention is that they may also find a use for features such
|
||||||
|
as "Message of the day".
|
||||||
|
|
||||||
|
This feature is specific to Synapse, but uses standard Matrix communication mechanisms,
|
||||||
|
so should work with any Matrix client. For more details see ``docs/server_notices.md``
|
||||||
|
|
||||||
|
Further Server Notices/Consent Tracking Support:
|
||||||
|
|
||||||
|
* Allow overriding the server_notices user's avatar (PR #3273)
|
||||||
|
* Use the localpart in the consent uri (PR #3272)
|
||||||
|
* Support for putting %(consent_uri)s in messages (PR #3271)
|
||||||
|
* Block attempts to send server notices to remote users (PR #3270)
|
||||||
|
* Docs on consent bits (PR #3268)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Changes in synapse v0.30.0-rc1 (2018-05-23)
|
||||||
|
==========================================
|
||||||
|
|
||||||
|
Server Notices/Consent Tracking Support:
|
||||||
|
|
||||||
|
* ConsentResource to gather policy consent from users (PR #3213)
|
||||||
|
* Move RoomCreationHandler out of synapse.handlers.Handlers (PR #3225)
|
||||||
|
* Infrastructure for a server notices room (PR #3232)
|
||||||
|
* Send users a server notice about consent (PR #3236)
|
||||||
|
* Reject attempts to send event before privacy consent is given (PR #3257)
|
||||||
|
* Add a 'has_consented' template var to consent forms (PR #3262)
|
||||||
|
* Fix dependency on jinja2 (PR #3263)
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
* Cohort analytics (PR #3163, #3241, #3251)
|
||||||
|
* Add lxml to docker image for web previews (PR #3239) Thanks to @ptman!
|
||||||
|
* Add in flight request metrics (PR #3252)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
* Remove unused `update_external_syncs` (PR #3233)
|
||||||
|
* Use stream rather depth ordering for push actions (PR #3212)
|
||||||
|
* Make purge_history operate on tokens (PR #3221)
|
||||||
|
* Don't support limitless pagination (PR #3265)
|
||||||
|
|
||||||
|
Bug Fixes:
|
||||||
|
|
||||||
|
* Fix logcontext resource usage tracking (PR #3258)
|
||||||
|
* Fix error in handling receipts (PR #3235)
|
||||||
|
* Stop the transaction cache caching failures (PR #3255)
|
||||||
|
|
||||||
|
Changes in synapse v0.29.1 (2018-05-17)
|
||||||
|
==========================================
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
* Update docker documentation (PR #3222)
|
||||||
|
|
||||||
|
Changes in synapse v0.29.0 (2018-05-16)
|
||||||
|
===========================================
|
||||||
|
Not changes since v0.29.0-rc1
|
||||||
|
|
||||||
|
Changes in synapse v0.29.0-rc1 (2018-05-14)
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
Notable changes, a docker file for running Synapse (Thanks to @kaiyou!) and a
|
||||||
|
closed spec bug in the Client Server API. Additionally further prep for Python 3
|
||||||
|
migration.
|
||||||
|
|
||||||
|
Potentially breaking change:
|
||||||
|
|
||||||
|
* Make Client-Server API return 401 for invalid token (PR #3161).
|
||||||
|
|
||||||
|
This changes the Client-server spec to return a 401 error code instead of 403
|
||||||
|
when the access token is unrecognised. This is the behaviour required by the
|
||||||
|
specification, but some clients may be relying on the old, incorrect
|
||||||
|
behaviour.
|
||||||
|
|
||||||
|
Thanks to @NotAFile for fixing this.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
* Add a Dockerfile for synapse (PR #2846) Thanks to @kaiyou!
|
||||||
|
|
||||||
|
Changes - General:
|
||||||
|
|
||||||
|
* nuke-room-from-db.sh: added postgresql option and help (PR #2337) Thanks to @rubo77!
|
||||||
|
* Part user from rooms on account deactivate (PR #3201)
|
||||||
|
* Make 'unexpected logging context' into warnings (PR #3007)
|
||||||
|
* Set Server header in SynapseRequest (PR #3208)
|
||||||
|
* remove duplicates from groups tables (PR #3129)
|
||||||
|
* Improve exception handling for background processes (PR #3138)
|
||||||
|
* Add missing consumeErrors to improve exception handling (PR #3139)
|
||||||
|
* reraise exceptions more carefully (PR #3142)
|
||||||
|
* Remove redundant call to preserve_fn (PR #3143)
|
||||||
|
* Trap exceptions thrown within run_in_background (PR #3144)
|
||||||
|
|
||||||
|
Changes - Refactors:
|
||||||
|
|
||||||
|
* Refactor /context to reuse pagination storage functions (PR #3193)
|
||||||
|
* Refactor recent events func to use pagination func (PR #3195)
|
||||||
|
* Refactor pagination DB API to return concrete type (PR #3196)
|
||||||
|
* Refactor get_recent_events_for_room return type (PR #3198)
|
||||||
|
* Refactor sync APIs to reuse pagination API (PR #3199)
|
||||||
|
* Remove unused code path from member change DB func (PR #3200)
|
||||||
|
* Refactor request handling wrappers (PR #3203)
|
||||||
|
* transaction_id, destination defined twice (PR #3209) Thanks to @damir-manapov!
|
||||||
|
* Refactor event storage to prepare for changes in state calculations (PR #3141)
|
||||||
|
* Set Server header in SynapseRequest (PR #3208)
|
||||||
|
* Use deferred.addTimeout instead of time_bound_deferred (PR #3127, #3178)
|
||||||
|
* Use run_in_background in preference to preserve_fn (PR #3140)
|
||||||
|
|
||||||
|
Changes - Python 3 migration:
|
||||||
|
|
||||||
|
* Construct HMAC as bytes on py3 (PR #3156) Thanks to @NotAFile!
|
||||||
|
* run config tests on py3 (PR #3159) Thanks to @NotAFile!
|
||||||
|
* Open certificate files as bytes (PR #3084) Thanks to @NotAFile!
|
||||||
|
* Open config file in non-bytes mode (PR #3085) Thanks to @NotAFile!
|
||||||
|
* Make event properties raise AttributeError instead (PR #3102) Thanks to @NotAFile!
|
||||||
|
* Use six.moves.urlparse (PR #3108) Thanks to @NotAFile!
|
||||||
|
* Add py3 tests to tox with folders that work (PR #3145) Thanks to @NotAFile!
|
||||||
|
* Don't yield in list comprehensions (PR #3150) Thanks to @NotAFile!
|
||||||
|
* Move more xrange to six (PR #3151) Thanks to @NotAFile!
|
||||||
|
* make imports local (PR #3152) Thanks to @NotAFile!
|
||||||
|
* move httplib import to six (PR #3153) Thanks to @NotAFile!
|
||||||
|
* Replace stringIO imports with six (PR #3154, #3168) Thanks to @NotAFile!
|
||||||
|
* more bytes strings (PR #3155) Thanks to @NotAFile!
|
||||||
|
|
||||||
|
Bug Fixes:
|
||||||
|
|
||||||
|
* synapse fails to start under Twisted >= 18.4 (PR #3157)
|
||||||
|
* Fix a class of logcontext leaks (PR #3170)
|
||||||
|
* Fix a couple of logcontext leaks in unit tests (PR #3172)
|
||||||
|
* Fix logcontext leak in media repo (PR #3174)
|
||||||
|
* Escape label values in prometheus metrics (PR #3175, #3186)
|
||||||
|
* Fix 'Unhandled Error' logs with Twisted 18.4 (PR #3182) Thanks to @Half-Shot!
|
||||||
|
* Fix logcontext leaks in rate limiter (PR #3183)
|
||||||
|
* notifications: Convert next_token to string according to the spec (PR #3190) Thanks to @mujx!
|
||||||
|
* nuke-room-from-db.sh: fix deletion from search table (PR #3194) Thanks to @rubo77!
|
||||||
|
* add guard for None on purge_history api (PR #3160) Thanks to @krombel!
|
||||||
|
|
||||||
|
Changes in synapse v0.28.1 (2018-05-01)
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
SECURITY UPDATE
|
||||||
|
|
||||||
|
* Clamp the allowed values of event depth received over federation to be
|
||||||
|
[0, 2^63 - 1]. This mitigates an attack where malicious events
|
||||||
|
injected with depth = 2^63 - 1 render rooms unusable. Depth is used to
|
||||||
|
determine the cosmetic ordering of events within a room, and so the ordering
|
||||||
|
of events in such a room will default to using stream_ordering rather than depth
|
||||||
|
(topological_ordering).
|
||||||
|
|
||||||
|
This is a temporary solution to mitigate abuse in the wild, whilst a long term solution
|
||||||
|
is being implemented to improve how the depth parameter is used.
|
||||||
|
|
||||||
|
Full details at
|
||||||
|
https://docs.google.com/document/d/1I3fi2S-XnpO45qrpCsowZv8P8dHcNZ4fsBsbOW7KABI
|
||||||
|
|
||||||
|
* Pin Twisted to <18.4 until we stop using the private _OpenSSLECCurve API.
|
||||||
|
|
||||||
|
|
||||||
|
Changes in synapse v0.28.0 (2018-04-26)
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
Bug Fixes:
|
||||||
|
|
||||||
|
* Fix quarantine media admin API and search reindex (PR #3130)
|
||||||
|
* Fix media admin APIs (PR #3134)
|
||||||
|
|
||||||
|
|
||||||
|
Changes in synapse v0.28.0-rc1 (2018-04-24)
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
Minor performance improvement to federation sending and bug fixes.
|
||||||
|
|
||||||
|
(Note: This release does not include the delta state resolution implementation discussed in matrix live)
|
||||||
|
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
* Add metrics for event processing lag (PR #3090)
|
||||||
|
* Add metrics for ResponseCache (PR #3092)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
* Synapse on PyPy (PR #2760) Thanks to @Valodim!
|
||||||
|
* move handling of auto_join_rooms to RegisterHandler (PR #2996) Thanks to @krombel!
|
||||||
|
* Improve handling of SRV records for federation connections (PR #3016) Thanks to @silkeh!
|
||||||
|
* Document the behaviour of ResponseCache (PR #3059)
|
||||||
|
* Preparation for py3 (PR #3061, #3073, #3074, #3075, #3103, #3104, #3106, #3107, #3109, #3110) Thanks to @NotAFile!
|
||||||
|
* update prometheus dashboard to use new metric names (PR #3069) Thanks to @krombel!
|
||||||
|
* use python3-compatible prints (PR #3074) Thanks to @NotAFile!
|
||||||
|
* Send federation events concurrently (PR #3078)
|
||||||
|
* Limit concurrent event sends for a room (PR #3079)
|
||||||
|
* Improve R30 stat definition (PR #3086)
|
||||||
|
* Send events to ASes concurrently (PR #3088)
|
||||||
|
* Refactor ResponseCache usage (PR #3093)
|
||||||
|
* Clarify that SRV may not point to a CNAME (PR #3100) Thanks to @silkeh!
|
||||||
|
* Use str(e) instead of e.message (PR #3103) Thanks to @NotAFile!
|
||||||
|
* Use six.itervalues in some places (PR #3106) Thanks to @NotAFile!
|
||||||
|
* Refactor store.have_events (PR #3117)
|
||||||
|
|
||||||
|
Bug Fixes:
|
||||||
|
|
||||||
|
* Return 401 for invalid access_token on logout (PR #2938) Thanks to @dklug!
|
||||||
|
* Return a 404 rather than a 500 on rejoining empty rooms (PR #3080)
|
||||||
|
* fix federation_domain_whitelist (PR #3099)
|
||||||
|
* Avoid creating events with huge numbers of prev_events (PR #3113)
|
||||||
|
* Reject events which have lots of prev_events (PR #3118)
|
||||||
|
|
||||||
|
|
||||||
|
Changes in synapse v0.27.4 (2018-04-13)
|
||||||
|
======================================
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
* Update canonicaljson dependency (#3095)
|
||||||
|
|
||||||
|
|
||||||
|
Changes in synapse v0.27.3 (2018-04-11)
|
||||||
|
======================================
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
|
||||||
|
* URL quote path segments over federation (#3082)
|
||||||
|
|
||||||
Changes in synapse v0.27.3-rc2 (2018-04-09)
|
Changes in synapse v0.27.3-rc2 (2018-04-09)
|
||||||
==========================================
|
==========================================
|
||||||
|
|
||||||
v0.27.3-rc1 used a stale version of the develop branch so the changelog overstates
|
v0.27.3-rc1 used a stale version of the develop branch so the changelog overstates
|
||||||
the functionality. v0.27.3-rc2 is up to date, rc1 should be ignored.
|
the functionality. v0.27.3-rc2 is up to date, rc1 should be ignored.
|
||||||
|
|
||||||
Changes in synapse v0.27.3-rc1 (2018-04-09)
|
Changes in synapse v0.27.3-rc1 (2018-04-09)
|
||||||
=======================================
|
=======================================
|
||||||
|
|
||||||
Notable changes include API support for joinability of groups. Also new metrics
|
Notable changes include API support for joinability of groups. Also new metrics
|
||||||
and phone home stats. Phone home stats include better visibility of system usage
|
and phone home stats. Phone home stats include better visibility of system usage
|
||||||
so we can tweak synpase to work better for all users rather than our own experience
|
so we can tweak synpase to work better for all users rather than our own experience
|
||||||
with matrix.org. Also, recording 'r30' stat which is the measure we use to track
|
with matrix.org. Also, recording 'r30' stat which is the measure we use to track
|
||||||
overal growth of the Matrix ecosystem. It is defined as:-
|
overal growth of the Matrix ecosystem. It is defined as:-
|
||||||
|
|
||||||
Counts the number of native 30 day retained users, defined as:-
|
Counts the number of native 30 day retained users, defined as:-
|
||||||
@@ -45,7 +284,6 @@ Bug fixes:
|
|||||||
|
|
||||||
* Add room_id to the response of `rooms/{roomId}/join` (PR #2986) Thanks to @jplatte!
|
* Add room_id to the response of `rooms/{roomId}/join` (PR #2986) Thanks to @jplatte!
|
||||||
* Fix replication after switch to simplejson (PR #3015)
|
* Fix replication after switch to simplejson (PR #3015)
|
||||||
* Fix replication after switch to simplejson (PR #3015)
|
|
||||||
* 404 correctly on missing paths via NoResource (PR #3022)
|
* 404 correctly on missing paths via NoResource (PR #3022)
|
||||||
* Fix error when claiming e2e keys from offline servers (PR #3034)
|
* Fix error when claiming e2e keys from offline servers (PR #3034)
|
||||||
* fix tests/storage/test_user_directory.py (PR #3042)
|
* fix tests/storage/test_user_directory.py (PR #3042)
|
||||||
|
|||||||
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
FROM docker.io/python:2-alpine3.7
|
||||||
|
|
||||||
|
RUN apk add --no-cache --virtual .nacl_deps su-exec build-base libffi-dev zlib-dev libressl-dev libjpeg-turbo-dev linux-headers postgresql-dev libxslt-dev
|
||||||
|
|
||||||
|
COPY . /synapse
|
||||||
|
|
||||||
|
# A wheel cache may be provided in ./cache for faster build
|
||||||
|
RUN cd /synapse \
|
||||||
|
&& pip install --upgrade pip setuptools psycopg2 lxml \
|
||||||
|
&& mkdir -p /synapse/cache \
|
||||||
|
&& pip install -f /synapse/cache --upgrade --process-dependency-links . \
|
||||||
|
&& mv /synapse/contrib/docker/start.py /synapse/contrib/docker/conf / \
|
||||||
|
&& rm -rf setup.py setup.cfg synapse
|
||||||
|
|
||||||
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
EXPOSE 8008/tcp 8448/tcp
|
||||||
|
|
||||||
|
ENTRYPOINT ["/start.py"]
|
||||||
@@ -25,6 +25,8 @@ recursive-include synapse/static *.js
|
|||||||
exclude jenkins.sh
|
exclude jenkins.sh
|
||||||
exclude jenkins*.sh
|
exclude jenkins*.sh
|
||||||
exclude jenkins*
|
exclude jenkins*
|
||||||
|
exclude Dockerfile
|
||||||
|
exclude .dockerignore
|
||||||
recursive-exclude jenkins *.sh
|
recursive-exclude jenkins *.sh
|
||||||
|
|
||||||
prune .github
|
prune .github
|
||||||
|
|||||||
@@ -157,8 +157,9 @@ if you prefer.
|
|||||||
|
|
||||||
In case of problems, please see the _`Troubleshooting` section below.
|
In case of problems, please see the _`Troubleshooting` section below.
|
||||||
|
|
||||||
Alternatively, Silvio Fricke has contributed a Dockerfile to automate the
|
There is an offical synapse image available at https://hub.docker.com/r/matrixdotorg/synapse/tags/ which can be used with the docker-compose file available at `contrib/docker`. Further information on this including configuration options is available in `contrib/docker/README.md`.
|
||||||
above in Docker at https://registry.hub.docker.com/u/silviof/docker-matrix/.
|
|
||||||
|
Alternatively, Andreas Peters (previously Silvio Fricke) has contributed a Dockerfile to automate a synapse server in a single Docker image, at https://hub.docker.com/r/avhost/docker-matrix/tags/
|
||||||
|
|
||||||
Also, Martin Giess has created an auto-deployment process with vagrant/ansible,
|
Also, Martin Giess has created an auto-deployment process with vagrant/ansible,
|
||||||
tested with VirtualBox/AWS/DigitalOcean - see https://github.com/EMnify/matrix-synapse-auto-deploy
|
tested with VirtualBox/AWS/DigitalOcean - see https://github.com/EMnify/matrix-synapse-auto-deploy
|
||||||
@@ -614,6 +615,9 @@ should have the format ``_matrix._tcp.<yourdomain.com> <ttl> IN SRV 10 0 <port>
|
|||||||
$ dig -t srv _matrix._tcp.example.com
|
$ dig -t srv _matrix._tcp.example.com
|
||||||
_matrix._tcp.example.com. 3600 IN SRV 10 0 8448 synapse.example.com.
|
_matrix._tcp.example.com. 3600 IN SRV 10 0 8448 synapse.example.com.
|
||||||
|
|
||||||
|
Note that the server hostname cannot be an alias (CNAME record): it has to point
|
||||||
|
directly to the server hosting the synapse instance.
|
||||||
|
|
||||||
You can then configure your homeserver to use ``<yourdomain.com>`` as the domain in
|
You can then configure your homeserver to use ``<yourdomain.com>`` as the domain in
|
||||||
its user-ids, by setting ``server_name``::
|
its user-ids, by setting ``server_name``::
|
||||||
|
|
||||||
|
|||||||
10
contrib/README.rst
Normal file
10
contrib/README.rst
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Community Contributions
|
||||||
|
=======================
|
||||||
|
|
||||||
|
Everything in this directory are projects submitted by the community that may be useful
|
||||||
|
to others. As such, the project maintainers cannot guarantee support, stability
|
||||||
|
or backwards compatibility of these projects.
|
||||||
|
|
||||||
|
Files in this directory should *not* be relied on directly, as they may not
|
||||||
|
continue to work or exist in future. If you wish to use any of these files then
|
||||||
|
they should be copied to avoid them breaking from underneath you.
|
||||||
153
contrib/docker/README.md
Normal file
153
contrib/docker/README.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
# Synapse Docker
|
||||||
|
|
||||||
|
The `matrixdotorg/synapse` Docker image will run Synapse as a single process. It does not provide a
|
||||||
|
database server or a TURN server, you should run these separately.
|
||||||
|
|
||||||
|
If you run a Postgres server, you should simply include it in the same Compose
|
||||||
|
project or set the proper environment variables and the image will automatically
|
||||||
|
use that server.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Build the docker image with the `docker build` command from the root of the synapse repository.
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -t docker.io/matrixdotorg/synapse .
|
||||||
|
```
|
||||||
|
|
||||||
|
The `-t` option sets the image tag. Official images are tagged `matrixdotorg/synapse:<version>` where `<version>` is the same as the release tag in the synapse git repository.
|
||||||
|
|
||||||
|
You may have a local Python wheel cache available, in which case copy the relevant packages in the ``cache/`` directory at the root of the project.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
This image is designed to run either with an automatically generated configuration
|
||||||
|
file or with a custom configuration that requires manual edition.
|
||||||
|
|
||||||
|
### Automated configuration
|
||||||
|
|
||||||
|
It is recommended that you use Docker Compose to run your containers, including
|
||||||
|
this image and a Postgres server. A sample ``docker-compose.yml`` is provided,
|
||||||
|
including example labels for reverse proxying and other artifacts.
|
||||||
|
|
||||||
|
Read the section about environment variables and set at least mandatory variables,
|
||||||
|
then run the server:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
If secrets are not specified in the environment variables, they will be generated
|
||||||
|
as part of the startup. Please ensure these secrets are kept between launches of the
|
||||||
|
Docker container, as their loss may require users to log in again.
|
||||||
|
|
||||||
|
### Manual configuration
|
||||||
|
|
||||||
|
A sample ``docker-compose.yml`` is provided, including example labels for
|
||||||
|
reverse proxying and other artifacts. The docker-compose file is an example,
|
||||||
|
please comment/uncomment sections that are not suitable for your usecase.
|
||||||
|
|
||||||
|
Specify a ``SYNAPSE_CONFIG_PATH``, preferably to a persistent path,
|
||||||
|
to use manual configuration. To generate a fresh ``homeserver.yaml``, simply run:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-compose run --rm -e SYNAPSE_SERVER_NAME=my.matrix.host synapse generate
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, customize your configuration and run the server:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Without Compose
|
||||||
|
|
||||||
|
If you do not wish to use Compose, you may still run this image using plain
|
||||||
|
Docker commands. Note that the following is just a guideline and you may need
|
||||||
|
to add parameters to the docker run command to account for the network situation
|
||||||
|
with your postgres database.
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run \
|
||||||
|
-d \
|
||||||
|
--name synapse \
|
||||||
|
-v ${DATA_PATH}:/data \
|
||||||
|
-e SYNAPSE_SERVER_NAME=my.matrix.host \
|
||||||
|
-e SYNAPSE_REPORT_STATS=yes \
|
||||||
|
docker.io/matrixdotorg/synapse:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Volumes
|
||||||
|
|
||||||
|
The image expects a single volume, located at ``/data``, that will hold:
|
||||||
|
|
||||||
|
* temporary files during uploads;
|
||||||
|
* uploaded media and thumbnails;
|
||||||
|
* the SQLite database if you do not configure postgres;
|
||||||
|
* the appservices configuration.
|
||||||
|
|
||||||
|
You are free to use separate volumes depending on storage endpoints at your
|
||||||
|
disposal. For instance, ``/data/media`` coud be stored on a large but low
|
||||||
|
performance hdd storage while other files could be stored on high performance
|
||||||
|
endpoints.
|
||||||
|
|
||||||
|
In order to setup an application service, simply create an ``appservices``
|
||||||
|
directory in the data volume and write the application service Yaml
|
||||||
|
configuration file there. Multiple application services are supported.
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
Unless you specify a custom path for the configuration file, a very generic
|
||||||
|
file will be generated, based on the following environment settings.
|
||||||
|
These are a good starting point for setting up your own deployment.
|
||||||
|
|
||||||
|
Global settings:
|
||||||
|
|
||||||
|
* ``UID``, the user id Synapse will run as [default 991]
|
||||||
|
* ``GID``, the group id Synapse will run as [default 991]
|
||||||
|
* ``SYNAPSE_CONFIG_PATH``, path to a custom config file
|
||||||
|
|
||||||
|
If ``SYNAPSE_CONFIG_PATH`` is set, you should generate a configuration file
|
||||||
|
then customize it manually. No other environment variable is required.
|
||||||
|
|
||||||
|
Otherwise, a dynamic configuration file will be used. The following environment
|
||||||
|
variables are available for configuration:
|
||||||
|
|
||||||
|
* ``SYNAPSE_SERVER_NAME`` (mandatory), the current server public hostname.
|
||||||
|
* ``SYNAPSE_REPORT_STATS``, (mandatory, ``yes`` or ``no``), enable anonymous
|
||||||
|
statistics reporting back to the Matrix project which helps us to get funding.
|
||||||
|
* ``SYNAPSE_NO_TLS``, set this variable to disable TLS in Synapse (use this if
|
||||||
|
you run your own TLS-capable reverse proxy).
|
||||||
|
* ``SYNAPSE_ENABLE_REGISTRATION``, set this variable to enable registration on
|
||||||
|
the Synapse instance.
|
||||||
|
* ``SYNAPSE_ALLOW_GUEST``, set this variable to allow guest joining this server.
|
||||||
|
* ``SYNAPSE_EVENT_CACHE_SIZE``, the event cache size [default `10K`].
|
||||||
|
* ``SYNAPSE_CACHE_FACTOR``, the cache factor [default `0.5`].
|
||||||
|
* ``SYNAPSE_RECAPTCHA_PUBLIC_KEY``, set this variable to the recaptcha public
|
||||||
|
key in order to enable recaptcha upon registration.
|
||||||
|
* ``SYNAPSE_RECAPTCHA_PRIVATE_KEY``, set this variable to the recaptcha private
|
||||||
|
key in order to enable recaptcha upon registration.
|
||||||
|
* ``SYNAPSE_TURN_URIS``, set this variable to the coma-separated list of TURN
|
||||||
|
uris to enable TURN for this homeserver.
|
||||||
|
* ``SYNAPSE_TURN_SECRET``, set this to the TURN shared secret if required.
|
||||||
|
|
||||||
|
Shared secrets, that will be initialized to random values if not set:
|
||||||
|
|
||||||
|
* ``SYNAPSE_REGISTRATION_SHARED_SECRET``, secret for registrering users if
|
||||||
|
registration is disable.
|
||||||
|
* ``SYNAPSE_MACAROON_SECRET_KEY`` secret for signing access tokens
|
||||||
|
to the server.
|
||||||
|
|
||||||
|
Database specific values (will use SQLite if not set):
|
||||||
|
|
||||||
|
* `POSTGRES_DB` - The database name for the synapse postgres database. [default: `synapse`]
|
||||||
|
* `POSTGRES_HOST` - The host of the postgres database if you wish to use postgresql instead of sqlite3. [default: `db` which is useful when using a container on the same docker network in a compose file where the postgres service is called `db`]
|
||||||
|
* `POSTGRES_PASSWORD` - The password for the synapse postgres database. **If this is set then postgres will be used instead of sqlite3.** [default: none] **NOTE**: You are highly encouraged to use postgresql! Please use the compose file to make it easier to deploy.
|
||||||
|
* `POSTGRES_USER` - The user for the synapse postgres database. [default: `matrix`]
|
||||||
|
|
||||||
|
Mail server specific values (will not send emails if not set):
|
||||||
|
|
||||||
|
* ``SYNAPSE_SMTP_HOST``, hostname to the mail server.
|
||||||
|
* ``SYNAPSE_SMTP_PORT``, TCP port for accessing the mail server [default ``25``].
|
||||||
|
* ``SYNAPSE_SMTP_USER``, username for authenticating against the mail server if any.
|
||||||
|
* ``SYNAPSE_SMTP_PASSWORD``, password for authenticating against the mail server if any.
|
||||||
219
contrib/docker/conf/homeserver.yaml
Normal file
219
contrib/docker/conf/homeserver.yaml
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
# vim:ft=yaml
|
||||||
|
|
||||||
|
## TLS ##
|
||||||
|
|
||||||
|
tls_certificate_path: "/data/{{ SYNAPSE_SERVER_NAME }}.tls.crt"
|
||||||
|
tls_private_key_path: "/data/{{ SYNAPSE_SERVER_NAME }}.tls.key"
|
||||||
|
tls_dh_params_path: "/data/{{ SYNAPSE_SERVER_NAME }}.tls.dh"
|
||||||
|
no_tls: {{ "True" if SYNAPSE_NO_TLS else "False" }}
|
||||||
|
tls_fingerprints: []
|
||||||
|
|
||||||
|
## Server ##
|
||||||
|
|
||||||
|
server_name: "{{ SYNAPSE_SERVER_NAME }}"
|
||||||
|
pid_file: /homeserver.pid
|
||||||
|
web_client: False
|
||||||
|
soft_file_limit: 0
|
||||||
|
|
||||||
|
## Ports ##
|
||||||
|
|
||||||
|
listeners:
|
||||||
|
{% if not SYNAPSE_NO_TLS %}
|
||||||
|
-
|
||||||
|
port: 8448
|
||||||
|
bind_addresses: ['0.0.0.0']
|
||||||
|
type: http
|
||||||
|
tls: true
|
||||||
|
x_forwarded: false
|
||||||
|
resources:
|
||||||
|
- names: [client]
|
||||||
|
compress: true
|
||||||
|
- names: [federation] # Federation APIs
|
||||||
|
compress: false
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
- port: 8008
|
||||||
|
tls: false
|
||||||
|
bind_addresses: ['0.0.0.0']
|
||||||
|
type: http
|
||||||
|
x_forwarded: false
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- names: [client]
|
||||||
|
compress: true
|
||||||
|
- names: [federation]
|
||||||
|
compress: false
|
||||||
|
|
||||||
|
## Database ##
|
||||||
|
|
||||||
|
{% if POSTGRES_PASSWORD %}
|
||||||
|
database:
|
||||||
|
name: "psycopg2"
|
||||||
|
args:
|
||||||
|
user: "{{ POSTGRES_USER or "synapse" }}"
|
||||||
|
password: "{{ POSTGRES_PASSWORD }}"
|
||||||
|
database: "{{ POSTGRES_DB or "synapse" }}"
|
||||||
|
host: "{{ POSTGRES_HOST or "db" }}"
|
||||||
|
port: "{{ POSTGRES_PORT or "5432" }}"
|
||||||
|
cp_min: 5
|
||||||
|
cp_max: 10
|
||||||
|
{% else %}
|
||||||
|
database:
|
||||||
|
name: "sqlite3"
|
||||||
|
args:
|
||||||
|
database: "/data/homeserver.db"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
## Performance ##
|
||||||
|
|
||||||
|
event_cache_size: "{{ SYNAPSE_EVENT_CACHE_SIZE or "10K" }}"
|
||||||
|
verbose: 0
|
||||||
|
log_file: "/data/homeserver.log"
|
||||||
|
log_config: "/compiled/log.config"
|
||||||
|
|
||||||
|
## Ratelimiting ##
|
||||||
|
|
||||||
|
rc_messages_per_second: 0.2
|
||||||
|
rc_message_burst_count: 10.0
|
||||||
|
federation_rc_window_size: 1000
|
||||||
|
federation_rc_sleep_limit: 10
|
||||||
|
federation_rc_sleep_delay: 500
|
||||||
|
federation_rc_reject_limit: 50
|
||||||
|
federation_rc_concurrent: 3
|
||||||
|
|
||||||
|
## Files ##
|
||||||
|
|
||||||
|
media_store_path: "/data/media"
|
||||||
|
uploads_path: "/data/uploads"
|
||||||
|
max_upload_size: "10M"
|
||||||
|
max_image_pixels: "32M"
|
||||||
|
dynamic_thumbnails: false
|
||||||
|
|
||||||
|
# List of thumbnail to precalculate when an image is uploaded.
|
||||||
|
thumbnail_sizes:
|
||||||
|
- width: 32
|
||||||
|
height: 32
|
||||||
|
method: crop
|
||||||
|
- width: 96
|
||||||
|
height: 96
|
||||||
|
method: crop
|
||||||
|
- width: 320
|
||||||
|
height: 240
|
||||||
|
method: scale
|
||||||
|
- width: 640
|
||||||
|
height: 480
|
||||||
|
method: scale
|
||||||
|
- width: 800
|
||||||
|
height: 600
|
||||||
|
method: scale
|
||||||
|
|
||||||
|
url_preview_enabled: False
|
||||||
|
max_spider_size: "10M"
|
||||||
|
|
||||||
|
## Captcha ##
|
||||||
|
|
||||||
|
{% if SYNAPSE_RECAPTCHA_PUBLIC_KEY %}
|
||||||
|
recaptcha_public_key: "{{ SYNAPSE_RECAPTCHA_PUBLIC_KEY }}"
|
||||||
|
recaptcha_private_key: "{{ SYNAPSE_RECAPTCHA_PRIVATE_KEY }}"
|
||||||
|
enable_registration_captcha: True
|
||||||
|
recaptcha_siteverify_api: "https://www.google.com/recaptcha/api/siteverify"
|
||||||
|
{% else %}
|
||||||
|
recaptcha_public_key: "YOUR_PUBLIC_KEY"
|
||||||
|
recaptcha_private_key: "YOUR_PRIVATE_KEY"
|
||||||
|
enable_registration_captcha: False
|
||||||
|
recaptcha_siteverify_api: "https://www.google.com/recaptcha/api/siteverify"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
## Turn ##
|
||||||
|
|
||||||
|
{% if SYNAPSE_TURN_URIS %}
|
||||||
|
turn_uris:
|
||||||
|
{% for uri in SYNAPSE_TURN_URIS.split(',') %} - "{{ uri }}"
|
||||||
|
{% endfor %}
|
||||||
|
turn_shared_secret: "{{ SYNAPSE_TURN_SECRET }}"
|
||||||
|
turn_user_lifetime: "1h"
|
||||||
|
turn_allow_guests: True
|
||||||
|
{% else %}
|
||||||
|
turn_uris: []
|
||||||
|
turn_shared_secret: "YOUR_SHARED_SECRET"
|
||||||
|
turn_user_lifetime: "1h"
|
||||||
|
turn_allow_guests: True
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
## Registration ##
|
||||||
|
|
||||||
|
enable_registration: {{ "True" if SYNAPSE_ENABLE_REGISTRATION else "False" }}
|
||||||
|
registration_shared_secret: "{{ SYNAPSE_REGISTRATION_SHARED_SECRET }}"
|
||||||
|
bcrypt_rounds: 12
|
||||||
|
allow_guest_access: {{ "True" if SYNAPSE_ALLOW_GUEST else "False" }}
|
||||||
|
enable_group_creation: true
|
||||||
|
|
||||||
|
# The list of identity servers trusted to verify third party
|
||||||
|
# identifiers by this server.
|
||||||
|
trusted_third_party_id_servers:
|
||||||
|
- matrix.org
|
||||||
|
- vector.im
|
||||||
|
- riot.im
|
||||||
|
|
||||||
|
## Metrics ###
|
||||||
|
|
||||||
|
{% if SYNAPSE_REPORT_STATS.lower() == "yes" %}
|
||||||
|
enable_metrics: True
|
||||||
|
report_stats: True
|
||||||
|
{% else %}
|
||||||
|
enable_metrics: False
|
||||||
|
report_stats: False
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
## API Configuration ##
|
||||||
|
|
||||||
|
room_invite_state_types:
|
||||||
|
- "m.room.join_rules"
|
||||||
|
- "m.room.canonical_alias"
|
||||||
|
- "m.room.avatar"
|
||||||
|
- "m.room.name"
|
||||||
|
|
||||||
|
{% if SYNAPSE_APPSERVICES %}
|
||||||
|
app_service_config_files:
|
||||||
|
{% for appservice in SYNAPSE_APPSERVICES %} - "{{ appservice }}"
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
app_service_config_files: []
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
macaroon_secret_key: "{{ SYNAPSE_MACAROON_SECRET_KEY }}"
|
||||||
|
expire_access_token: False
|
||||||
|
|
||||||
|
## Signing Keys ##
|
||||||
|
|
||||||
|
signing_key_path: "/data/{{ SYNAPSE_SERVER_NAME }}.signing.key"
|
||||||
|
old_signing_keys: {}
|
||||||
|
key_refresh_interval: "1d" # 1 Day.
|
||||||
|
|
||||||
|
# The trusted servers to download signing keys from.
|
||||||
|
perspectives:
|
||||||
|
servers:
|
||||||
|
"matrix.org":
|
||||||
|
verify_keys:
|
||||||
|
"ed25519:auto":
|
||||||
|
key: "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw"
|
||||||
|
|
||||||
|
password_config:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
{% if SYNAPSE_SMTP_HOST %}
|
||||||
|
email:
|
||||||
|
enable_notifs: false
|
||||||
|
smtp_host: "{{ SYNAPSE_SMTP_HOST }}"
|
||||||
|
smtp_port: {{ SYNAPSE_SMTP_PORT or "25" }}
|
||||||
|
smtp_user: "{{ SYNAPSE_SMTP_USER }}"
|
||||||
|
smtp_pass: "{{ SYNAPSE_SMTP_PASSWORD }}"
|
||||||
|
require_transport_security: False
|
||||||
|
notif_from: "{{ SYNAPSE_SMTP_FROM or "hostmaster@" + SYNAPSE_SERVER_NAME }}"
|
||||||
|
app_name: Matrix
|
||||||
|
template_dir: res/templates
|
||||||
|
notif_template_html: notif_mail.html
|
||||||
|
notif_template_text: notif_mail.txt
|
||||||
|
notif_for_new_users: True
|
||||||
|
riot_base_url: "https://{{ SYNAPSE_SERVER_NAME }}"
|
||||||
|
{% endif %}
|
||||||
29
contrib/docker/conf/log.config
Normal file
29
contrib/docker/conf/log.config
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
version: 1
|
||||||
|
|
||||||
|
formatters:
|
||||||
|
precise:
|
||||||
|
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s- %(message)s'
|
||||||
|
|
||||||
|
filters:
|
||||||
|
context:
|
||||||
|
(): synapse.util.logcontext.LoggingContextFilter
|
||||||
|
request: ""
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
console:
|
||||||
|
class: logging.StreamHandler
|
||||||
|
formatter: precise
|
||||||
|
filters: [context]
|
||||||
|
|
||||||
|
loggers:
|
||||||
|
synapse:
|
||||||
|
level: {{ SYNAPSE_LOG_LEVEL or "WARNING" }}
|
||||||
|
|
||||||
|
synapse.storage.SQL:
|
||||||
|
# beware: increasing this to DEBUG will make synapse log sensitive
|
||||||
|
# information such as access tokens.
|
||||||
|
level: {{ SYNAPSE_LOG_LEVEL or "WARNING" }}
|
||||||
|
|
||||||
|
root:
|
||||||
|
level: {{ SYNAPSE_LOG_LEVEL or "WARNING" }}
|
||||||
|
handlers: [console]
|
||||||
49
contrib/docker/docker-compose.yml
Normal file
49
contrib/docker/docker-compose.yml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# This compose file is compatible with Compose itself, it might need some
|
||||||
|
# adjustments to run properly with stack.
|
||||||
|
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
synapse:
|
||||||
|
image: docker.io/matrixdotorg/synapse:latest
|
||||||
|
# Since snyapse does not retry to connect to the database, restart upon
|
||||||
|
# failure
|
||||||
|
restart: unless-stopped
|
||||||
|
# See the readme for a full documentation of the environment settings
|
||||||
|
environment:
|
||||||
|
- SYNAPSE_SERVER_NAME=my.matrix.host
|
||||||
|
- SYNAPSE_REPORT_STATS=no
|
||||||
|
- SYNAPSE_ENABLE_REGISTRATION=yes
|
||||||
|
- SYNAPSE_LOG_LEVEL=INFO
|
||||||
|
- POSTGRES_PASSWORD=changeme
|
||||||
|
volumes:
|
||||||
|
# You may either store all the files in a local folder
|
||||||
|
- ./files:/data
|
||||||
|
# .. or you may split this between different storage points
|
||||||
|
# - ./files:/data
|
||||||
|
# - /path/to/ssd:/data/uploads
|
||||||
|
# - /path/to/large_hdd:/data/media
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
# In order to expose Synapse, remove one of the following, you might for
|
||||||
|
# instance expose the TLS port directly:
|
||||||
|
ports:
|
||||||
|
- 8448:8448/tcp
|
||||||
|
# ... or use a reverse proxy, here is an example for traefik:
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.frontend.rule=Host:my.matrix.Host
|
||||||
|
- traefik.port=8448
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: docker.io/postgres:10-alpine
|
||||||
|
# Change that password, of course!
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=synapse
|
||||||
|
- POSTGRES_PASSWORD=changeme
|
||||||
|
volumes:
|
||||||
|
# You may store the database tables in a local folder..
|
||||||
|
- ./schemas:/var/lib/postgresql/data
|
||||||
|
# .. or store them on some high performance storage for better results
|
||||||
|
# - /path/to/ssd/storage:/var/lib/postfesql/data
|
||||||
66
contrib/docker/start.py
Executable file
66
contrib/docker/start.py
Executable file
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/local/bin/python
|
||||||
|
|
||||||
|
import jinja2
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import glob
|
||||||
|
|
||||||
|
# Utility functions
|
||||||
|
convert = lambda src, dst, environ: open(dst, "w").write(jinja2.Template(open(src).read()).render(**environ))
|
||||||
|
|
||||||
|
def check_arguments(environ, args):
|
||||||
|
for argument in args:
|
||||||
|
if argument not in environ:
|
||||||
|
print("Environment variable %s is mandatory, exiting." % argument)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
def generate_secrets(environ, secrets):
|
||||||
|
for name, secret in secrets.items():
|
||||||
|
if secret not in environ:
|
||||||
|
filename = "/data/%s.%s.key" % (environ["SYNAPSE_SERVER_NAME"], name)
|
||||||
|
if os.path.exists(filename):
|
||||||
|
with open(filename) as handle: value = handle.read()
|
||||||
|
else:
|
||||||
|
print("Generating a random secret for {}".format(name))
|
||||||
|
value = os.urandom(32).encode("hex")
|
||||||
|
with open(filename, "w") as handle: handle.write(value)
|
||||||
|
environ[secret] = value
|
||||||
|
|
||||||
|
# Prepare the configuration
|
||||||
|
mode = sys.argv[1] if len(sys.argv) > 1 else None
|
||||||
|
environ = os.environ.copy()
|
||||||
|
ownership = "{}:{}".format(environ.get("UID", 991), environ.get("GID", 991))
|
||||||
|
args = ["python", "-m", "synapse.app.homeserver"]
|
||||||
|
|
||||||
|
# In generate mode, generate a configuration, missing keys, then exit
|
||||||
|
if mode == "generate":
|
||||||
|
check_arguments(environ, ("SYNAPSE_SERVER_NAME", "SYNAPSE_REPORT_STATS", "SYNAPSE_CONFIG_PATH"))
|
||||||
|
args += [
|
||||||
|
"--server-name", environ["SYNAPSE_SERVER_NAME"],
|
||||||
|
"--report-stats", environ["SYNAPSE_REPORT_STATS"],
|
||||||
|
"--config-path", environ["SYNAPSE_CONFIG_PATH"],
|
||||||
|
"--generate-config"
|
||||||
|
]
|
||||||
|
os.execv("/usr/local/bin/python", args)
|
||||||
|
|
||||||
|
# In normal mode, generate missing keys if any, then run synapse
|
||||||
|
else:
|
||||||
|
# Parse the configuration file
|
||||||
|
if "SYNAPSE_CONFIG_PATH" in environ:
|
||||||
|
args += ["--config-path", environ["SYNAPSE_CONFIG_PATH"]]
|
||||||
|
else:
|
||||||
|
check_arguments(environ, ("SYNAPSE_SERVER_NAME", "SYNAPSE_REPORT_STATS"))
|
||||||
|
generate_secrets(environ, {
|
||||||
|
"registration": "SYNAPSE_REGISTRATION_SHARED_SECRET",
|
||||||
|
"macaroon": "SYNAPSE_MACAROON_SECRET_KEY"
|
||||||
|
})
|
||||||
|
environ["SYNAPSE_APPSERVICES"] = glob.glob("/data/appservices/*.yaml")
|
||||||
|
if not os.path.exists("/compiled"): os.mkdir("/compiled")
|
||||||
|
convert("/conf/homeserver.yaml", "/compiled/homeserver.yaml", environ)
|
||||||
|
convert("/conf/log.config", "/compiled/log.config", environ)
|
||||||
|
subprocess.check_output(["chown", "-R", ownership, "/data"])
|
||||||
|
args += ["--config-path", "/compiled/homeserver.yaml"]
|
||||||
|
# Generate missing keys and start synapse
|
||||||
|
subprocess.check_output(args + ["--generate-keys"])
|
||||||
|
os.execv("/sbin/su-exec", ["su-exec", ownership] + args)
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
# (e.g. https://www.archlinux.org/packages/community/any/matrix-synapse/ for ArchLinux)
|
# (e.g. https://www.archlinux.org/packages/community/any/matrix-synapse/ for ArchLinux)
|
||||||
# rather than in a user home directory or similar under virtualenv.
|
# rather than in a user home directory or similar under virtualenv.
|
||||||
|
|
||||||
|
# **NOTE:** This is an example service file that may change in the future. If you
|
||||||
|
# wish to use this please copy rather than symlink it.
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Synapse Matrix homeserver
|
Description=Synapse Matrix homeserver
|
||||||
|
|
||||||
@@ -12,6 +15,7 @@ Group=synapse
|
|||||||
WorkingDirectory=/var/lib/synapse
|
WorkingDirectory=/var/lib/synapse
|
||||||
ExecStart=/usr/bin/python2.7 -m synapse.app.homeserver --config-path=/etc/synapse/homeserver.yaml
|
ExecStart=/usr/bin/python2.7 -m synapse.app.homeserver --config-path=/etc/synapse/homeserver.yaml
|
||||||
ExecStop=/usr/bin/synctl stop /etc/synapse/homeserver.yaml
|
ExecStop=/usr/bin/synctl stop /etc/synapse/homeserver.yaml
|
||||||
|
# EnvironmentFile=-/etc/sysconfig/synapse # Can be used to e.g. set SYNAPSE_CACHE_FACTOR
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
160
docs/consent_tracking.md
Normal file
160
docs/consent_tracking.md
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
Support in Synapse for tracking agreement to server terms and conditions
|
||||||
|
========================================================================
|
||||||
|
|
||||||
|
Synapse 0.30 introduces support for tracking whether users have agreed to the
|
||||||
|
terms and conditions set by the administrator of a server - and blocking access
|
||||||
|
to the server until they have.
|
||||||
|
|
||||||
|
There are several parts to this functionality; each requires some specific
|
||||||
|
configuration in `homeserver.yaml` to be enabled.
|
||||||
|
|
||||||
|
Note that various parts of the configuation and this document refer to the
|
||||||
|
"privacy policy": agreement with a privacy policy is one particular use of this
|
||||||
|
feature, but of course adminstrators can specify other terms and conditions
|
||||||
|
unrelated to "privacy" per se.
|
||||||
|
|
||||||
|
Collecting policy agreement from a user
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
Synapse can be configured to serve the user a simple policy form with an
|
||||||
|
"accept" button. Clicking "Accept" records the user's acceptance in the
|
||||||
|
database and shows a success page.
|
||||||
|
|
||||||
|
To enable this, first create templates for the policy and success pages.
|
||||||
|
These should be stored on the local filesystem.
|
||||||
|
|
||||||
|
These templates use the [Jinja2](http://jinja.pocoo.org) templating language,
|
||||||
|
and [docs/privacy_policy_templates](privacy_policy_templates) gives
|
||||||
|
examples of the sort of thing that can be done.
|
||||||
|
|
||||||
|
Note that the templates must be stored under a name giving the language of the
|
||||||
|
template - currently this must always be `en` (for "English");
|
||||||
|
internationalisation support is intended for the future.
|
||||||
|
|
||||||
|
The template for the policy itself should be versioned and named according to
|
||||||
|
the version: for example `1.0.html`. The version of the policy which the user
|
||||||
|
has agreed to is stored in the database.
|
||||||
|
|
||||||
|
Once the templates are in place, make the following changes to `homeserver.yaml`:
|
||||||
|
|
||||||
|
1. Add a `user_consent` section, which should look like:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
user_consent:
|
||||||
|
template_dir: privacy_policy_templates
|
||||||
|
version: 1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
`template_dir` points to the directory containing the policy
|
||||||
|
templates. `version` defines the version of the policy which will be served
|
||||||
|
to the user. In the example above, Synapse will serve
|
||||||
|
`privacy_policy_templates/en/1.0.html`.
|
||||||
|
|
||||||
|
|
||||||
|
2. Add a `form_secret` setting at the top level:
|
||||||
|
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
form_secret: "<unique secret>"
|
||||||
|
```
|
||||||
|
|
||||||
|
This should be set to an arbitrary secret string (try `pwgen -y 30` to
|
||||||
|
generate suitable secrets).
|
||||||
|
|
||||||
|
More on what this is used for below.
|
||||||
|
|
||||||
|
3. Add `consent` wherever the `client` resource is currently enabled in the
|
||||||
|
`listeners` configuration. For example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
listeners:
|
||||||
|
- port: 8008
|
||||||
|
resources:
|
||||||
|
- names:
|
||||||
|
- client
|
||||||
|
- consent
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Finally, ensure that `jinja2` is installed. If you are using a virtualenv, this
|
||||||
|
should be a matter of `pip install Jinja2`. On debian, try `apt-get install
|
||||||
|
python-jinja2`.
|
||||||
|
|
||||||
|
Once this is complete, and the server has been restarted, try visiting
|
||||||
|
`https://<server>/_matrix/consent`. If correctly configured, this should give
|
||||||
|
an error "Missing string query parameter 'u'". It is now possible to manually
|
||||||
|
construct URIs where users can give their consent.
|
||||||
|
|
||||||
|
### Constructing the consent URI
|
||||||
|
|
||||||
|
It may be useful to manually construct the "consent URI" for a given user - for
|
||||||
|
instance, in order to send them an email asking them to consent. To do this,
|
||||||
|
take the base `https://<server>/_matrix/consent` URL and add the following
|
||||||
|
query parameters:
|
||||||
|
|
||||||
|
* `u`: the user id of the user. This can either be a full MXID
|
||||||
|
(`@user:server.com`) or just the localpart (`user`).
|
||||||
|
|
||||||
|
* `h`: hex-encoded HMAC-SHA256 of `u` using the `form_secret` as a key. It is
|
||||||
|
possible to calculate this on the commandline with something like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo -n '<user>' | openssl sha256 -hmac '<form_secret>'
|
||||||
|
```
|
||||||
|
|
||||||
|
This should result in a URI which looks something like:
|
||||||
|
`https://<server>/_matrix/consent?u=<user>&h=68a152465a4d...`.
|
||||||
|
|
||||||
|
|
||||||
|
Sending users a server notice asking them to agree to the policy
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
It is possible to configure Synapse to send a [server
|
||||||
|
notice](server_notices.md) to anybody who has not yet agreed to the current
|
||||||
|
version of the policy. To do so:
|
||||||
|
|
||||||
|
* ensure that the consent resource is configured, as in the previous section
|
||||||
|
|
||||||
|
* ensure that server notices are configured, as in [server_notices.md](server_notices.md).
|
||||||
|
|
||||||
|
* Add `server_notice_content` under `user_consent` in `homeserver.yaml`. For
|
||||||
|
example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
user_consent:
|
||||||
|
server_notice_content:
|
||||||
|
msgtype: m.text
|
||||||
|
body: >-
|
||||||
|
Please give your consent to the privacy policy at %(consent_uri)s.
|
||||||
|
```
|
||||||
|
|
||||||
|
Synapse automatically replaces the placeholder `%(consent_uri)s` with the
|
||||||
|
consent uri for that user.
|
||||||
|
|
||||||
|
* ensure that `public_baseurl` is set in `homeserver.yaml`, and gives the base
|
||||||
|
URI that clients use to connect to the server. (It is used to construct
|
||||||
|
`consent_uri` in the server notice.)
|
||||||
|
|
||||||
|
|
||||||
|
Blocking users from using the server until they agree to the policy
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
|
||||||
|
Synapse can be configured to block any attempts to join rooms or send messages
|
||||||
|
until the user has given their agreement to the policy. (Joining the server
|
||||||
|
notices room is exempted from this).
|
||||||
|
|
||||||
|
To enable this, add `block_events_error` under `user_consent`. For example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
user_consent:
|
||||||
|
block_events_error: >-
|
||||||
|
You can't send any messages until you consent to the privacy policy at
|
||||||
|
%(consent_uri)s.
|
||||||
|
```
|
||||||
|
|
||||||
|
Synapse automatically replaces the placeholder `%(consent_uri)s` with the
|
||||||
|
consent uri for that user.
|
||||||
|
|
||||||
|
ensure that `public_baseurl` is set in `homeserver.yaml`, and gives the base
|
||||||
|
URI that clients use to connect to the server. (It is used to construct
|
||||||
|
`consent_uri` in the error.)
|
||||||
43
docs/manhole.md
Normal file
43
docs/manhole.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
Using the synapse manhole
|
||||||
|
=========================
|
||||||
|
|
||||||
|
The "manhole" allows server administrators to access a Python shell on a running
|
||||||
|
Synapse installation. This is a very powerful mechanism for administration and
|
||||||
|
debugging.
|
||||||
|
|
||||||
|
To enable it, first uncomment the `manhole` listener configuration in
|
||||||
|
`homeserver.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
listeners:
|
||||||
|
- port: 9000
|
||||||
|
bind_addresses: ['::1', '127.0.0.1']
|
||||||
|
type: manhole
|
||||||
|
```
|
||||||
|
|
||||||
|
(`bind_addresses` in the above is important: it ensures that access to the
|
||||||
|
manhole is only possible for local users).
|
||||||
|
|
||||||
|
Note that this will give administrative access to synapse to **all users** with
|
||||||
|
shell access to the server. It should therefore **not** be enabled in
|
||||||
|
environments where untrusted users have shell access.
|
||||||
|
|
||||||
|
Then restart synapse, and point an ssh client at port 9000 on localhost, using
|
||||||
|
the username `matrix`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -p9000 matrix@localhost
|
||||||
|
```
|
||||||
|
|
||||||
|
The password is `rabbithole`.
|
||||||
|
|
||||||
|
This gives a Python REPL in which `hs` gives access to the
|
||||||
|
`synapse.server.HomeServer` object - which in turn gives access to many other
|
||||||
|
parts of the process.
|
||||||
|
|
||||||
|
As a simple example, retrieving an event from the database:
|
||||||
|
|
||||||
|
```
|
||||||
|
>>> hs.get_datastore().get_event('$1416420717069yeQaw:matrix.org')
|
||||||
|
<Deferred at 0x7ff253fc6998 current result: <FrozenEvent event_id='$1416420717069yeQaw:matrix.org', type='m.room.create', state_key=''>>
|
||||||
|
```
|
||||||
@@ -6,7 +6,13 @@ Postgres version 9.4 or later is known to work.
|
|||||||
Set up database
|
Set up database
|
||||||
===============
|
===============
|
||||||
|
|
||||||
The PostgreSQL database used *must* have the correct encoding set, otherwise
|
Assuming your PostgreSQL database user is called ``postgres``, create a user
|
||||||
|
``synapse_user`` with::
|
||||||
|
|
||||||
|
su - postgres
|
||||||
|
createuser --pwprompt synapse_user
|
||||||
|
|
||||||
|
The PostgreSQL database used *must* have the correct encoding set, otherwise it
|
||||||
would not be able to store UTF8 strings. To create a database with the correct
|
would not be able to store UTF8 strings. To create a database with the correct
|
||||||
encoding use, e.g.::
|
encoding use, e.g.::
|
||||||
|
|
||||||
@@ -46,8 +52,8 @@ As with Debian/Ubuntu, postgres support depends on the postgres python connector
|
|||||||
Synapse config
|
Synapse config
|
||||||
==============
|
==============
|
||||||
|
|
||||||
When you are ready to start using PostgreSQL, add the following line to your
|
When you are ready to start using PostgreSQL, edit the ``database`` section in
|
||||||
config file::
|
your config file to match the following lines::
|
||||||
|
|
||||||
database:
|
database:
|
||||||
name: psycopg2
|
name: psycopg2
|
||||||
@@ -96,9 +102,12 @@ complete, restart synapse. For instance::
|
|||||||
cp homeserver.db homeserver.db.snapshot
|
cp homeserver.db homeserver.db.snapshot
|
||||||
./synctl start
|
./synctl start
|
||||||
|
|
||||||
Assuming your new config file (as described in the section *Synapse config*)
|
Copy the old config file into a new config file::
|
||||||
is named ``homeserver-postgres.yaml`` and the SQLite snapshot is at
|
|
||||||
``homeserver.db.snapshot`` then simply run::
|
cp homeserver.yaml homeserver-postgres.yaml
|
||||||
|
|
||||||
|
Edit the database section as described in the section *Synapse config* above
|
||||||
|
and with the SQLite snapshot located at ``homeserver.db.snapshot`` simply run::
|
||||||
|
|
||||||
synapse_port_db --sqlite-database homeserver.db.snapshot \
|
synapse_port_db --sqlite-database homeserver.db.snapshot \
|
||||||
--postgres-config homeserver-postgres.yaml
|
--postgres-config homeserver-postgres.yaml
|
||||||
@@ -117,6 +126,11 @@ run::
|
|||||||
--postgres-config homeserver-postgres.yaml
|
--postgres-config homeserver-postgres.yaml
|
||||||
|
|
||||||
Once that has completed, change the synapse config to point at the PostgreSQL
|
Once that has completed, change the synapse config to point at the PostgreSQL
|
||||||
database configuration file ``homeserver-postgres.yaml`` (i.e. rename it to
|
database configuration file ``homeserver-postgres.yaml``:
|
||||||
``homeserver.yaml``) and restart synapse. Synapse should now be running against
|
|
||||||
PostgreSQL.
|
./synctl stop
|
||||||
|
mv homeserver.yaml homeserver-old-sqlite.yaml
|
||||||
|
mv homeserver-postgres.yaml homeserver.yaml
|
||||||
|
./synctl start
|
||||||
|
|
||||||
|
Synapse should now be running against PostgreSQL.
|
||||||
|
|||||||
23
docs/privacy_policy_templates/en/1.0.html
Normal file
23
docs/privacy_policy_templates/en/1.0.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Matrix.org Privacy policy</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% if has_consented %}
|
||||||
|
<p>
|
||||||
|
Your base already belong to us.
|
||||||
|
</p>
|
||||||
|
{% else %}
|
||||||
|
<p>
|
||||||
|
All your base are belong to us.
|
||||||
|
</p>
|
||||||
|
<form method="post" action="consent">
|
||||||
|
<input type="hidden" name="v" value="{{version}}"/>
|
||||||
|
<input type="hidden" name="u" value="{{user}}"/>
|
||||||
|
<input type="hidden" name="h" value="{{userhmac}}"/>
|
||||||
|
<input type="submit" value="Sure thing!"/>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
11
docs/privacy_policy_templates/en/success.html
Normal file
11
docs/privacy_policy_templates/en/success.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Matrix.org Privacy policy</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
Sweet.
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
71
docs/server_notices.md
Normal file
71
docs/server_notices.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
Server Notices
|
||||||
|
==============
|
||||||
|
|
||||||
|
'Server Notices' are a new feature introduced in Synapse 0.30. They provide a
|
||||||
|
channel whereby server administrators can send messages to users on the server.
|
||||||
|
|
||||||
|
They are used as part of communication of the server polices(see
|
||||||
|
[consent_tracking.md](consent_tracking.md)), however the intention is that
|
||||||
|
they may also find a use for features such as "Message of the day".
|
||||||
|
|
||||||
|
This is a feature specific to Synapse, but it uses standard Matrix
|
||||||
|
communication mechanisms, so should work with any Matrix client.
|
||||||
|
|
||||||
|
User experience
|
||||||
|
---------------
|
||||||
|
|
||||||
|
When the user is first sent a server notice, they will get an invitation to a
|
||||||
|
room (typically called 'Server Notices', though this is configurable in
|
||||||
|
`homeserver.yaml`). They will be **unable to reject** this invitation -
|
||||||
|
attempts to do so will receive an error.
|
||||||
|
|
||||||
|
Once they accept the invitation, they will see the notice message in the room
|
||||||
|
history; it will appear to have come from the 'server notices user' (see
|
||||||
|
below).
|
||||||
|
|
||||||
|
The user is prevented from sending any messages in this room by the power
|
||||||
|
levels. They also cannot leave it.
|
||||||
|
|
||||||
|
Synapse configuration
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Server notices come from a specific user id on the server. Server
|
||||||
|
administrators are free to choose the user id - something like `server` is
|
||||||
|
suggested, meaning the notices will come from
|
||||||
|
`@server:<your_server_name>`. Once the Server Notices user is configured, that
|
||||||
|
user id becomes a special, privileged user, so administrators should ensure
|
||||||
|
that **it is not already allocated**.
|
||||||
|
|
||||||
|
In order to support server notices, it is necessary to add some configuration
|
||||||
|
to the `homeserver.yaml` file. In particular, you should add a `server_notices`
|
||||||
|
section, which should look like this:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
server_notices:
|
||||||
|
system_mxid_localpart: server
|
||||||
|
system_mxid_display_name: "Server Notices"
|
||||||
|
system_mxid_avatar_url: "mxc://server.com/oumMVlgDnLYFaPVkExemNVVZ"
|
||||||
|
room_name: "Server Notices"
|
||||||
|
```
|
||||||
|
|
||||||
|
The only compulsory setting is `system_mxid_localpart`, which defines the user
|
||||||
|
id of the Server Notices user, as above. `room_name` defines the name of the
|
||||||
|
room which will be created.
|
||||||
|
|
||||||
|
`system_mxid_display_name` and `system_mxid_avatar_url` can be used to set the
|
||||||
|
displayname and avatar of the Server Notices user.
|
||||||
|
|
||||||
|
Sending notices
|
||||||
|
---------------
|
||||||
|
|
||||||
|
As of the current version of synapse, there is no convenient interface for
|
||||||
|
sending notices (other than the automated ones sent as part of consent
|
||||||
|
tracking).
|
||||||
|
|
||||||
|
In the meantime, it is possible to test this feature using the manhole. Having
|
||||||
|
gone into the manhole as described in [manhole.md](manhole.md), a notice can be
|
||||||
|
sent with something like:
|
||||||
|
|
||||||
|
```
|
||||||
|
>>> hs.get_server_notices_manager().send_notice('@user:server.com', {'msgtype':'m.text', 'body':'foo'})
|
||||||
|
```
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#! /bin/bash
|
#! /bin/bash
|
||||||
|
|
||||||
|
set -eux
|
||||||
|
|
||||||
cd "`dirname $0`/.."
|
cd "`dirname $0`/.."
|
||||||
|
|
||||||
TOX_DIR=$WORKSPACE/.tox
|
TOX_DIR=$WORKSPACE/.tox
|
||||||
@@ -14,7 +16,20 @@ fi
|
|||||||
tox -e py27 --notest -v
|
tox -e py27 --notest -v
|
||||||
|
|
||||||
TOX_BIN=$TOX_DIR/py27/bin
|
TOX_BIN=$TOX_DIR/py27/bin
|
||||||
$TOX_BIN/pip install setuptools
|
|
||||||
|
# cryptography 2.2 requires setuptools >= 18.5.
|
||||||
|
#
|
||||||
|
# older versions of virtualenv (?) give us a virtualenv with the same version
|
||||||
|
# of setuptools as is installed on the system python (and tox runs virtualenv
|
||||||
|
# under python3, so we get the version of setuptools that is installed on that).
|
||||||
|
#
|
||||||
|
# anyway, make sure that we have a recent enough setuptools.
|
||||||
|
$TOX_BIN/pip install 'setuptools>=18.5'
|
||||||
|
|
||||||
|
# we also need a semi-recent version of pip, because old ones fail to install
|
||||||
|
# the "enum34" dependency of cryptography.
|
||||||
|
$TOX_BIN/pip install 'pip>=10'
|
||||||
|
|
||||||
{ python synapse/python_dependencies.py
|
{ python synapse/python_dependencies.py
|
||||||
echo lxml psycopg2
|
echo lxml psycopg2
|
||||||
} | xargs $TOX_BIN/pip install
|
} | xargs $TOX_BIN/pip install
|
||||||
|
|||||||
@@ -6,9 +6,19 @@
|
|||||||
|
|
||||||
## Do not run it lightly.
|
## Do not run it lightly.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "$1" == "-h" ] || [ "$1" == "" ]; then
|
||||||
|
echo "Call with ROOM_ID as first option and then pipe it into the database. So for instance you might run"
|
||||||
|
echo " nuke-room-from-db.sh <room_id> | sqlite3 homeserver.db"
|
||||||
|
echo "or"
|
||||||
|
echo " nuke-room-from-db.sh <room_id> | psql --dbname=synapse"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
ROOMID="$1"
|
ROOMID="$1"
|
||||||
|
|
||||||
sqlite3 homeserver.db <<EOF
|
cat <<EOF
|
||||||
DELETE FROM event_forward_extremities WHERE room_id = '$ROOMID';
|
DELETE FROM event_forward_extremities WHERE room_id = '$ROOMID';
|
||||||
DELETE FROM event_backward_extremities WHERE room_id = '$ROOMID';
|
DELETE FROM event_backward_extremities WHERE room_id = '$ROOMID';
|
||||||
DELETE FROM event_edges WHERE room_id = '$ROOMID';
|
DELETE FROM event_edges WHERE room_id = '$ROOMID';
|
||||||
@@ -29,7 +39,7 @@ DELETE FROM state_groups WHERE room_id = '$ROOMID';
|
|||||||
DELETE FROM state_groups_state WHERE room_id = '$ROOMID';
|
DELETE FROM state_groups_state WHERE room_id = '$ROOMID';
|
||||||
DELETE FROM receipts_graph WHERE room_id = '$ROOMID';
|
DELETE FROM receipts_graph WHERE room_id = '$ROOMID';
|
||||||
DELETE FROM receipts_linearized WHERE room_id = '$ROOMID';
|
DELETE FROM receipts_linearized WHERE room_id = '$ROOMID';
|
||||||
DELETE FROM event_search_content WHERE c1room_id = '$ROOMID';
|
DELETE FROM event_search WHERE room_id = '$ROOMID';
|
||||||
DELETE FROM guest_access WHERE room_id = '$ROOMID';
|
DELETE FROM guest_access WHERE room_id = '$ROOMID';
|
||||||
DELETE FROM history_visibility WHERE room_id = '$ROOMID';
|
DELETE FROM history_visibility WHERE room_id = '$ROOMID';
|
||||||
DELETE FROM room_tags WHERE room_id = '$ROOMID';
|
DELETE FROM room_tags WHERE room_id = '$ROOMID';
|
||||||
|
|||||||
@@ -16,4 +16,4 @@
|
|||||||
""" This is a reference implementation of a Matrix home server.
|
""" This is a reference implementation of a Matrix home server.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "0.27.3-rc2"
|
__version__ = "0.30.0"
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
"""Contains constants from the specification."""
|
"""Contains constants from the specification."""
|
||||||
|
|
||||||
|
# the "depth" field on events is limited to 2**63 - 1
|
||||||
|
MAX_DEPTH = 2**63 - 1
|
||||||
|
|
||||||
|
|
||||||
class Membership(object):
|
class Membership(object):
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import simplejson as json
|
import simplejson as json
|
||||||
|
from six import iteritems
|
||||||
|
from six.moves import http_client
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -50,6 +52,8 @@ class Codes(object):
|
|||||||
THREEPID_DENIED = "M_THREEPID_DENIED"
|
THREEPID_DENIED = "M_THREEPID_DENIED"
|
||||||
INVALID_USERNAME = "M_INVALID_USERNAME"
|
INVALID_USERNAME = "M_INVALID_USERNAME"
|
||||||
SERVER_NOT_TRUSTED = "M_SERVER_NOT_TRUSTED"
|
SERVER_NOT_TRUSTED = "M_SERVER_NOT_TRUSTED"
|
||||||
|
CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN"
|
||||||
|
CANNOT_LEAVE_SERVER_NOTICE_ROOM = "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM"
|
||||||
|
|
||||||
|
|
||||||
class CodeMessageException(RuntimeError):
|
class CodeMessageException(RuntimeError):
|
||||||
@@ -137,6 +141,32 @@ class SynapseError(CodeMessageException):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class ConsentNotGivenError(SynapseError):
|
||||||
|
"""The error returned to the client when the user has not consented to the
|
||||||
|
privacy policy.
|
||||||
|
"""
|
||||||
|
def __init__(self, msg, consent_uri):
|
||||||
|
"""Constructs a ConsentNotGivenError
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (str): The human-readable error message
|
||||||
|
consent_url (str): The URL where the user can give their consent
|
||||||
|
"""
|
||||||
|
super(ConsentNotGivenError, self).__init__(
|
||||||
|
code=http_client.FORBIDDEN,
|
||||||
|
msg=msg,
|
||||||
|
errcode=Codes.CONSENT_NOT_GIVEN
|
||||||
|
)
|
||||||
|
self._consent_uri = consent_uri
|
||||||
|
|
||||||
|
def error_dict(self):
|
||||||
|
return cs_error(
|
||||||
|
self.msg,
|
||||||
|
self.errcode,
|
||||||
|
consent_uri=self._consent_uri
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RegistrationError(SynapseError):
|
class RegistrationError(SynapseError):
|
||||||
"""An error raised when a registration event fails."""
|
"""An error raised when a registration event fails."""
|
||||||
pass
|
pass
|
||||||
@@ -291,13 +321,13 @@ def cs_error(msg, code=Codes.UNKNOWN, **kwargs):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (str): The error message.
|
msg (str): The error message.
|
||||||
code (int): The error code.
|
code (str): The error code.
|
||||||
kwargs : Additional keys to add to the response.
|
kwargs : Additional keys to add to the response.
|
||||||
Returns:
|
Returns:
|
||||||
A dict representing the error response JSON.
|
A dict representing the error response JSON.
|
||||||
"""
|
"""
|
||||||
err = {"error": msg, "errcode": code}
|
err = {"error": msg, "errcode": code}
|
||||||
for key, value in kwargs.iteritems():
|
for key, value in iteritems(kwargs):
|
||||||
err[key] = value
|
err[key] = value
|
||||||
return err
|
return err
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014-2016 OpenMarket Ltd
|
||||||
|
# Copyright 2018 New Vector Ltd.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -14,6 +15,12 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
"""Contains the URL paths to prefix various aspects of the server with. """
|
"""Contains the URL paths to prefix various aspects of the server with. """
|
||||||
|
from hashlib import sha256
|
||||||
|
import hmac
|
||||||
|
|
||||||
|
from six.moves.urllib.parse import urlencode
|
||||||
|
|
||||||
|
from synapse.config import ConfigError
|
||||||
|
|
||||||
CLIENT_PREFIX = "/_matrix/client/api/v1"
|
CLIENT_PREFIX = "/_matrix/client/api/v1"
|
||||||
CLIENT_V2_ALPHA_PREFIX = "/_matrix/client/v2_alpha"
|
CLIENT_V2_ALPHA_PREFIX = "/_matrix/client/v2_alpha"
|
||||||
@@ -25,3 +32,46 @@ SERVER_KEY_PREFIX = "/_matrix/key/v1"
|
|||||||
SERVER_KEY_V2_PREFIX = "/_matrix/key/v2"
|
SERVER_KEY_V2_PREFIX = "/_matrix/key/v2"
|
||||||
MEDIA_PREFIX = "/_matrix/media/r0"
|
MEDIA_PREFIX = "/_matrix/media/r0"
|
||||||
LEGACY_MEDIA_PREFIX = "/_matrix/media/v1"
|
LEGACY_MEDIA_PREFIX = "/_matrix/media/v1"
|
||||||
|
|
||||||
|
|
||||||
|
class ConsentURIBuilder(object):
|
||||||
|
def __init__(self, hs_config):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
hs_config (synapse.config.homeserver.HomeServerConfig):
|
||||||
|
"""
|
||||||
|
if hs_config.form_secret is None:
|
||||||
|
raise ConfigError(
|
||||||
|
"form_secret not set in config",
|
||||||
|
)
|
||||||
|
if hs_config.public_baseurl is None:
|
||||||
|
raise ConfigError(
|
||||||
|
"public_baseurl not set in config",
|
||||||
|
)
|
||||||
|
|
||||||
|
self._hmac_secret = hs_config.form_secret.encode("utf-8")
|
||||||
|
self._public_baseurl = hs_config.public_baseurl
|
||||||
|
|
||||||
|
def build_user_consent_uri(self, user_id):
|
||||||
|
"""Build a URI which we can give to the user to do their privacy
|
||||||
|
policy consent
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id (str): mxid or username of user
|
||||||
|
|
||||||
|
Returns
|
||||||
|
(str) the URI where the user can do consent
|
||||||
|
"""
|
||||||
|
mac = hmac.new(
|
||||||
|
key=self._hmac_secret,
|
||||||
|
msg=user_id,
|
||||||
|
digestmod=sha256,
|
||||||
|
).hexdigest()
|
||||||
|
consent_uri = "%s_matrix/consent?%s" % (
|
||||||
|
self._public_baseurl,
|
||||||
|
urlencode({
|
||||||
|
"u": user_id,
|
||||||
|
"h": mac
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
return consent_uri
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ from synapse.replication.tcp.client import ReplicationClientHandler
|
|||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
from synapse.storage.engines import create_engine
|
from synapse.storage.engines import create_engine
|
||||||
from synapse.util.httpresourcetree import create_resource_tree
|
from synapse.util.httpresourcetree import create_resource_tree
|
||||||
from synapse.util.logcontext import LoggingContext, preserve_fn
|
from synapse.util.logcontext import LoggingContext, run_in_background
|
||||||
from synapse.util.manhole import manhole
|
from synapse.util.manhole import manhole
|
||||||
from synapse.util.versionstring import get_version_string
|
from synapse.util.versionstring import get_version_string
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor, defer
|
||||||
from twisted.web.resource import NoResource
|
from twisted.web.resource import NoResource
|
||||||
|
|
||||||
logger = logging.getLogger("synapse.app.appservice")
|
logger = logging.getLogger("synapse.app.appservice")
|
||||||
@@ -74,6 +74,7 @@ class AppserviceServer(HomeServer):
|
|||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -112,9 +113,14 @@ class ASReplicationHandler(ReplicationClientHandler):
|
|||||||
|
|
||||||
if stream_name == "events":
|
if stream_name == "events":
|
||||||
max_stream_id = self.store.get_room_max_stream_ordering()
|
max_stream_id = self.store.get_room_max_stream_ordering()
|
||||||
preserve_fn(
|
run_in_background(self._notify_app_services, max_stream_id)
|
||||||
self.appservice_handler.notify_interested_services
|
|
||||||
)(max_stream_id)
|
@defer.inlineCallbacks
|
||||||
|
def _notify_app_services(self, room_stream_id):
|
||||||
|
try:
|
||||||
|
yield self.appservice_handler.notify_interested_services(room_stream_id)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error notifying application services of event")
|
||||||
|
|
||||||
|
|
||||||
def start(config_options):
|
def start(config_options):
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ class ClientReaderServer(HomeServer):
|
|||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ class EventCreatorServer(HomeServer):
|
|||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ class FederationReaderServer(HomeServer):
|
|||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ from synapse.server import HomeServer
|
|||||||
from synapse.storage.engines import create_engine
|
from synapse.storage.engines import create_engine
|
||||||
from synapse.util.async import Linearizer
|
from synapse.util.async import Linearizer
|
||||||
from synapse.util.httpresourcetree import create_resource_tree
|
from synapse.util.httpresourcetree import create_resource_tree
|
||||||
from synapse.util.logcontext import LoggingContext, preserve_fn
|
from synapse.util.logcontext import LoggingContext, run_in_background
|
||||||
from synapse.util.manhole import manhole
|
from synapse.util.manhole import manhole
|
||||||
from synapse.util.versionstring import get_version_string
|
from synapse.util.versionstring import get_version_string
|
||||||
from twisted.internet import defer, reactor
|
from twisted.internet import defer, reactor
|
||||||
@@ -101,6 +101,7 @@ class FederationSenderServer(HomeServer):
|
|||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -229,7 +230,7 @@ class FederationSenderHandler(object):
|
|||||||
# presence, typing, etc.
|
# presence, typing, etc.
|
||||||
if stream_name == "federation":
|
if stream_name == "federation":
|
||||||
send_queue.process_rows_for_federation(self.federation_sender, rows)
|
send_queue.process_rows_for_federation(self.federation_sender, rows)
|
||||||
preserve_fn(self.update_token)(token)
|
run_in_background(self.update_token, token)
|
||||||
|
|
||||||
# We also need to poke the federation sender when new events happen
|
# We also need to poke the federation sender when new events happen
|
||||||
elif stream_name == "events":
|
elif stream_name == "events":
|
||||||
@@ -237,19 +238,22 @@ class FederationSenderHandler(object):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def update_token(self, token):
|
def update_token(self, token):
|
||||||
self.federation_position = token
|
try:
|
||||||
|
self.federation_position = token
|
||||||
|
|
||||||
# We linearize here to ensure we don't have races updating the token
|
# We linearize here to ensure we don't have races updating the token
|
||||||
with (yield self._fed_position_linearizer.queue(None)):
|
with (yield self._fed_position_linearizer.queue(None)):
|
||||||
if self._last_ack < self.federation_position:
|
if self._last_ack < self.federation_position:
|
||||||
yield self.store.update_federation_out_pos(
|
yield self.store.update_federation_out_pos(
|
||||||
"federation", self.federation_position
|
"federation", self.federation_position
|
||||||
)
|
)
|
||||||
|
|
||||||
# We ACK this token over replication so that the master can drop
|
# We ACK this token over replication so that the master can drop
|
||||||
# its in memory queues
|
# its in memory queues
|
||||||
self.replication_client.send_federation_ack(self.federation_position)
|
self.replication_client.send_federation_ack(self.federation_position)
|
||||||
self._last_ack = self.federation_position
|
self._last_ack = self.federation_position
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error updating federation stream position")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ class FrontendProxyServer(HomeServer):
|
|||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ class SynapseHomeServer(HomeServer):
|
|||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
),
|
),
|
||||||
self.tls_server_context_factory,
|
self.tls_server_context_factory,
|
||||||
)
|
)
|
||||||
@@ -153,6 +154,7 @@ class SynapseHomeServer(HomeServer):
|
|||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
logger.info("Synapse now listening on port %d", port)
|
logger.info("Synapse now listening on port %d", port)
|
||||||
@@ -182,6 +184,15 @@ class SynapseHomeServer(HomeServer):
|
|||||||
"/_matrix/client/versions": client_resource,
|
"/_matrix/client/versions": client_resource,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if name == "consent":
|
||||||
|
from synapse.rest.consent.consent_resource import ConsentResource
|
||||||
|
consent_resource = ConsentResource(self)
|
||||||
|
if compress:
|
||||||
|
consent_resource = gz_wrap(consent_resource)
|
||||||
|
resources.update({
|
||||||
|
"/_matrix/consent": consent_resource,
|
||||||
|
})
|
||||||
|
|
||||||
if name == "federation":
|
if name == "federation":
|
||||||
resources.update({
|
resources.update({
|
||||||
FEDERATION_PREFIX: TransportLayerServer(self),
|
FEDERATION_PREFIX: TransportLayerServer(self),
|
||||||
@@ -473,6 +484,14 @@ def run(hs):
|
|||||||
" changes across releases."
|
" changes across releases."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def generate_user_daily_visit_stats():
|
||||||
|
hs.get_datastore().generate_user_daily_visits()
|
||||||
|
|
||||||
|
# Rather than update on per session basis, batch up the requests.
|
||||||
|
# If you increase the loop period, the accuracy of user_daily_visits
|
||||||
|
# table will decrease
|
||||||
|
clock.looping_call(generate_user_daily_visit_stats, 5 * 60 * 1000)
|
||||||
|
|
||||||
if hs.config.report_stats:
|
if hs.config.report_stats:
|
||||||
logger.info("Scheduling stats reporting for 3 hour intervals")
|
logger.info("Scheduling stats reporting for 3 hour intervals")
|
||||||
clock.looping_call(phone_stats_home, 3 * 60 * 60 * 1000)
|
clock.looping_call(phone_stats_home, 3 * 60 * 60 * 1000)
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ class MediaRepositoryServer(HomeServer):
|
|||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ from synapse.server import HomeServer
|
|||||||
from synapse.storage import DataStore
|
from synapse.storage import DataStore
|
||||||
from synapse.storage.engines import create_engine
|
from synapse.storage.engines import create_engine
|
||||||
from synapse.util.httpresourcetree import create_resource_tree
|
from synapse.util.httpresourcetree import create_resource_tree
|
||||||
from synapse.util.logcontext import LoggingContext, preserve_fn
|
from synapse.util.logcontext import LoggingContext, run_in_background
|
||||||
from synapse.util.manhole import manhole
|
from synapse.util.manhole import manhole
|
||||||
from synapse.util.versionstring import get_version_string
|
from synapse.util.versionstring import get_version_string
|
||||||
from twisted.internet import defer, reactor
|
from twisted.internet import defer, reactor
|
||||||
@@ -104,6 +104,7 @@ class PusherServer(HomeServer):
|
|||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -140,24 +141,27 @@ class PusherReplicationHandler(ReplicationClientHandler):
|
|||||||
|
|
||||||
def on_rdata(self, stream_name, token, rows):
|
def on_rdata(self, stream_name, token, rows):
|
||||||
super(PusherReplicationHandler, self).on_rdata(stream_name, token, rows)
|
super(PusherReplicationHandler, self).on_rdata(stream_name, token, rows)
|
||||||
preserve_fn(self.poke_pushers)(stream_name, token, rows)
|
run_in_background(self.poke_pushers, stream_name, token, rows)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def poke_pushers(self, stream_name, token, rows):
|
def poke_pushers(self, stream_name, token, rows):
|
||||||
if stream_name == "pushers":
|
try:
|
||||||
for row in rows:
|
if stream_name == "pushers":
|
||||||
if row.deleted:
|
for row in rows:
|
||||||
yield self.stop_pusher(row.user_id, row.app_id, row.pushkey)
|
if row.deleted:
|
||||||
else:
|
yield self.stop_pusher(row.user_id, row.app_id, row.pushkey)
|
||||||
yield self.start_pusher(row.user_id, row.app_id, row.pushkey)
|
else:
|
||||||
elif stream_name == "events":
|
yield self.start_pusher(row.user_id, row.app_id, row.pushkey)
|
||||||
yield self.pusher_pool.on_new_notifications(
|
elif stream_name == "events":
|
||||||
token, token,
|
yield self.pusher_pool.on_new_notifications(
|
||||||
)
|
token, token,
|
||||||
elif stream_name == "receipts":
|
)
|
||||||
yield self.pusher_pool.on_new_receipts(
|
elif stream_name == "receipts":
|
||||||
token, token, set(row.room_id for row in rows)
|
yield self.pusher_pool.on_new_receipts(
|
||||||
)
|
token, token, set(row.room_id for row in rows)
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error poking pushers")
|
||||||
|
|
||||||
def stop_pusher(self, user_id, app_id, pushkey):
|
def stop_pusher(self, user_id, app_id, pushkey):
|
||||||
key = "%s:%s" % (app_id, pushkey)
|
key = "%s:%s" % (app_id, pushkey)
|
||||||
|
|||||||
@@ -51,13 +51,15 @@ from synapse.storage.engines import create_engine
|
|||||||
from synapse.storage.presence import UserPresenceState
|
from synapse.storage.presence import UserPresenceState
|
||||||
from synapse.storage.roommember import RoomMemberStore
|
from synapse.storage.roommember import RoomMemberStore
|
||||||
from synapse.util.httpresourcetree import create_resource_tree
|
from synapse.util.httpresourcetree import create_resource_tree
|
||||||
from synapse.util.logcontext import LoggingContext, preserve_fn
|
from synapse.util.logcontext import LoggingContext, run_in_background
|
||||||
from synapse.util.manhole import manhole
|
from synapse.util.manhole import manhole
|
||||||
from synapse.util.stringutils import random_string
|
from synapse.util.stringutils import random_string
|
||||||
from synapse.util.versionstring import get_version_string
|
from synapse.util.versionstring import get_version_string
|
||||||
from twisted.internet import defer, reactor
|
from twisted.internet import defer, reactor
|
||||||
from twisted.web.resource import NoResource
|
from twisted.web.resource import NoResource
|
||||||
|
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
logger = logging.getLogger("synapse.app.synchrotron")
|
logger = logging.getLogger("synapse.app.synchrotron")
|
||||||
|
|
||||||
|
|
||||||
@@ -211,7 +213,7 @@ class SynchrotronPresence(object):
|
|||||||
|
|
||||||
def get_currently_syncing_users(self):
|
def get_currently_syncing_users(self):
|
||||||
return [
|
return [
|
||||||
user_id for user_id, count in self.user_to_num_current_syncs.iteritems()
|
user_id for user_id, count in iteritems(self.user_to_num_current_syncs)
|
||||||
if count > 0
|
if count > 0
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -279,6 +281,7 @@ class SynchrotronServer(HomeServer):
|
|||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -325,8 +328,7 @@ class SyncReplicationHandler(ReplicationClientHandler):
|
|||||||
|
|
||||||
def on_rdata(self, stream_name, token, rows):
|
def on_rdata(self, stream_name, token, rows):
|
||||||
super(SyncReplicationHandler, self).on_rdata(stream_name, token, rows)
|
super(SyncReplicationHandler, self).on_rdata(stream_name, token, rows)
|
||||||
|
run_in_background(self.process_and_notify, stream_name, token, rows)
|
||||||
preserve_fn(self.process_and_notify)(stream_name, token, rows)
|
|
||||||
|
|
||||||
def get_streams_to_replicate(self):
|
def get_streams_to_replicate(self):
|
||||||
args = super(SyncReplicationHandler, self).get_streams_to_replicate()
|
args = super(SyncReplicationHandler, self).get_streams_to_replicate()
|
||||||
@@ -338,55 +340,58 @@ class SyncReplicationHandler(ReplicationClientHandler):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def process_and_notify(self, stream_name, token, rows):
|
def process_and_notify(self, stream_name, token, rows):
|
||||||
if stream_name == "events":
|
try:
|
||||||
# We shouldn't get multiple rows per token for events stream, so
|
if stream_name == "events":
|
||||||
# we don't need to optimise this for multiple rows.
|
# We shouldn't get multiple rows per token for events stream, so
|
||||||
for row in rows:
|
# we don't need to optimise this for multiple rows.
|
||||||
event = yield self.store.get_event(row.event_id)
|
for row in rows:
|
||||||
extra_users = ()
|
event = yield self.store.get_event(row.event_id)
|
||||||
if event.type == EventTypes.Member:
|
extra_users = ()
|
||||||
extra_users = (event.state_key,)
|
if event.type == EventTypes.Member:
|
||||||
max_token = self.store.get_room_max_stream_ordering()
|
extra_users = (event.state_key,)
|
||||||
self.notifier.on_new_room_event(
|
max_token = self.store.get_room_max_stream_ordering()
|
||||||
event, token, max_token, extra_users
|
self.notifier.on_new_room_event(
|
||||||
)
|
event, token, max_token, extra_users
|
||||||
elif stream_name == "push_rules":
|
)
|
||||||
self.notifier.on_new_event(
|
elif stream_name == "push_rules":
|
||||||
"push_rules_key", token, users=[row.user_id for row in rows],
|
|
||||||
)
|
|
||||||
elif stream_name in ("account_data", "tag_account_data",):
|
|
||||||
self.notifier.on_new_event(
|
|
||||||
"account_data_key", token, users=[row.user_id for row in rows],
|
|
||||||
)
|
|
||||||
elif stream_name == "receipts":
|
|
||||||
self.notifier.on_new_event(
|
|
||||||
"receipt_key", token, rooms=[row.room_id for row in rows],
|
|
||||||
)
|
|
||||||
elif stream_name == "typing":
|
|
||||||
self.typing_handler.process_replication_rows(token, rows)
|
|
||||||
self.notifier.on_new_event(
|
|
||||||
"typing_key", token, rooms=[row.room_id for row in rows],
|
|
||||||
)
|
|
||||||
elif stream_name == "to_device":
|
|
||||||
entities = [row.entity for row in rows if row.entity.startswith("@")]
|
|
||||||
if entities:
|
|
||||||
self.notifier.on_new_event(
|
self.notifier.on_new_event(
|
||||||
"to_device_key", token, users=entities,
|
"push_rules_key", token, users=[row.user_id for row in rows],
|
||||||
)
|
)
|
||||||
elif stream_name == "device_lists":
|
elif stream_name in ("account_data", "tag_account_data",):
|
||||||
all_room_ids = set()
|
self.notifier.on_new_event(
|
||||||
for row in rows:
|
"account_data_key", token, users=[row.user_id for row in rows],
|
||||||
room_ids = yield self.store.get_rooms_for_user(row.user_id)
|
)
|
||||||
all_room_ids.update(room_ids)
|
elif stream_name == "receipts":
|
||||||
self.notifier.on_new_event(
|
self.notifier.on_new_event(
|
||||||
"device_list_key", token, rooms=all_room_ids,
|
"receipt_key", token, rooms=[row.room_id for row in rows],
|
||||||
)
|
)
|
||||||
elif stream_name == "presence":
|
elif stream_name == "typing":
|
||||||
yield self.presence_handler.process_replication_rows(token, rows)
|
self.typing_handler.process_replication_rows(token, rows)
|
||||||
elif stream_name == "receipts":
|
self.notifier.on_new_event(
|
||||||
self.notifier.on_new_event(
|
"typing_key", token, rooms=[row.room_id for row in rows],
|
||||||
"groups_key", token, users=[row.user_id for row in rows],
|
)
|
||||||
)
|
elif stream_name == "to_device":
|
||||||
|
entities = [row.entity for row in rows if row.entity.startswith("@")]
|
||||||
|
if entities:
|
||||||
|
self.notifier.on_new_event(
|
||||||
|
"to_device_key", token, users=entities,
|
||||||
|
)
|
||||||
|
elif stream_name == "device_lists":
|
||||||
|
all_room_ids = set()
|
||||||
|
for row in rows:
|
||||||
|
room_ids = yield self.store.get_rooms_for_user(row.user_id)
|
||||||
|
all_room_ids.update(room_ids)
|
||||||
|
self.notifier.on_new_event(
|
||||||
|
"device_list_key", token, rooms=all_room_ids,
|
||||||
|
)
|
||||||
|
elif stream_name == "presence":
|
||||||
|
yield self.presence_handler.process_replication_rows(token, rows)
|
||||||
|
elif stream_name == "receipts":
|
||||||
|
self.notifier.on_new_event(
|
||||||
|
"groups_key", token, users=[row.user_id for row in rows],
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error processing replication")
|
||||||
|
|
||||||
|
|
||||||
def start(config_options):
|
def start(config_options):
|
||||||
|
|||||||
@@ -39,10 +39,10 @@ from synapse.storage.engines import create_engine
|
|||||||
from synapse.storage.user_directory import UserDirectoryStore
|
from synapse.storage.user_directory import UserDirectoryStore
|
||||||
from synapse.util.caches.stream_change_cache import StreamChangeCache
|
from synapse.util.caches.stream_change_cache import StreamChangeCache
|
||||||
from synapse.util.httpresourcetree import create_resource_tree
|
from synapse.util.httpresourcetree import create_resource_tree
|
||||||
from synapse.util.logcontext import LoggingContext, preserve_fn
|
from synapse.util.logcontext import LoggingContext, run_in_background
|
||||||
from synapse.util.manhole import manhole
|
from synapse.util.manhole import manhole
|
||||||
from synapse.util.versionstring import get_version_string
|
from synapse.util.versionstring import get_version_string
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor, defer
|
||||||
from twisted.web.resource import NoResource
|
from twisted.web.resource import NoResource
|
||||||
|
|
||||||
logger = logging.getLogger("synapse.app.user_dir")
|
logger = logging.getLogger("synapse.app.user_dir")
|
||||||
@@ -126,6 +126,7 @@ class UserDirectoryServer(HomeServer):
|
|||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -164,7 +165,14 @@ class UserDirectoryReplicationHandler(ReplicationClientHandler):
|
|||||||
stream_name, token, rows
|
stream_name, token, rows
|
||||||
)
|
)
|
||||||
if stream_name == "current_state_deltas":
|
if stream_name == "current_state_deltas":
|
||||||
preserve_fn(self.user_directory.notify_new_event)()
|
run_in_background(self._notify_directory)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _notify_directory(self):
|
||||||
|
try:
|
||||||
|
yield self.user_directory.notify_new_event()
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error notifiying user directory of state update")
|
||||||
|
|
||||||
|
|
||||||
def start(config_options):
|
def start(config_options):
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ from synapse.api.constants import ThirdPartyEntityKind
|
|||||||
from synapse.api.errors import CodeMessageException
|
from synapse.api.errors import CodeMessageException
|
||||||
from synapse.http.client import SimpleHttpClient
|
from synapse.http.client import SimpleHttpClient
|
||||||
from synapse.events.utils import serialize_event
|
from synapse.events.utils import serialize_event
|
||||||
from synapse.util.logcontext import preserve_fn, make_deferred_yieldable
|
|
||||||
from synapse.util.caches.response_cache import ResponseCache
|
from synapse.util.caches.response_cache import ResponseCache
|
||||||
from synapse.types import ThirdPartyInstanceID
|
from synapse.types import ThirdPartyInstanceID
|
||||||
|
|
||||||
@@ -194,12 +193,7 @@ class ApplicationServiceApi(SimpleHttpClient):
|
|||||||
defer.returnValue(None)
|
defer.returnValue(None)
|
||||||
|
|
||||||
key = (service.id, protocol)
|
key = (service.id, protocol)
|
||||||
result = self.protocol_meta_cache.get(key)
|
return self.protocol_meta_cache.wrap(key, _get)
|
||||||
if not result:
|
|
||||||
result = self.protocol_meta_cache.set(
|
|
||||||
key, preserve_fn(_get)()
|
|
||||||
)
|
|
||||||
return make_deferred_yieldable(result)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def push_bulk(self, service, events, txn_id=None):
|
def push_bulk(self, service, events, txn_id=None):
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ components.
|
|||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.appservice import ApplicationServiceState
|
from synapse.appservice import ApplicationServiceState
|
||||||
from synapse.util.logcontext import preserve_fn
|
from synapse.util.logcontext import run_in_background
|
||||||
from synapse.util.metrics import Measure
|
from synapse.util.metrics import Measure
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@@ -106,7 +106,7 @@ class _ServiceQueuer(object):
|
|||||||
def enqueue(self, service, event):
|
def enqueue(self, service, event):
|
||||||
# if this service isn't being sent something
|
# if this service isn't being sent something
|
||||||
self.queued_events.setdefault(service.id, []).append(event)
|
self.queued_events.setdefault(service.id, []).append(event)
|
||||||
preserve_fn(self._send_request)(service)
|
run_in_background(self._send_request, service)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _send_request(self, service):
|
def _send_request(self, service):
|
||||||
@@ -152,10 +152,10 @@ class _TransactionController(object):
|
|||||||
if sent:
|
if sent:
|
||||||
yield txn.complete(self.store)
|
yield txn.complete(self.store)
|
||||||
else:
|
else:
|
||||||
preserve_fn(self._start_recoverer)(service)
|
run_in_background(self._start_recoverer, service)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
logger.exception(e)
|
logger.exception("Error creating appservice transaction")
|
||||||
preserve_fn(self._start_recoverer)(service)
|
run_in_background(self._start_recoverer, service)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_recovered(self, recoverer):
|
def on_recovered(self, recoverer):
|
||||||
@@ -176,17 +176,20 @@ class _TransactionController(object):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _start_recoverer(self, service):
|
def _start_recoverer(self, service):
|
||||||
yield self.store.set_appservice_state(
|
try:
|
||||||
service,
|
yield self.store.set_appservice_state(
|
||||||
ApplicationServiceState.DOWN
|
service,
|
||||||
)
|
ApplicationServiceState.DOWN
|
||||||
logger.info(
|
)
|
||||||
"Application service falling behind. Starting recoverer. AS ID %s",
|
logger.info(
|
||||||
service.id
|
"Application service falling behind. Starting recoverer. AS ID %s",
|
||||||
)
|
service.id
|
||||||
recoverer = self.recoverer_fn(service, self.on_recovered)
|
)
|
||||||
self.add_recoverers([recoverer])
|
recoverer = self.recoverer_fn(service, self.on_recovered)
|
||||||
recoverer.recover()
|
self.add_recoverers([recoverer])
|
||||||
|
recoverer.recover()
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error starting AS recoverer")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _is_service_up(self, service):
|
def _is_service_up(self, service):
|
||||||
|
|||||||
@@ -12,3 +12,9 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
from ._base import ConfigError
|
||||||
|
|
||||||
|
# export ConfigError if somebody does import *
|
||||||
|
# this is largely a fudge to stop PEP8 moaning about the import
|
||||||
|
__all__ = ["ConfigError"]
|
||||||
|
|||||||
@@ -281,15 +281,15 @@ class Config(object):
|
|||||||
)
|
)
|
||||||
if not cls.path_exists(config_dir_path):
|
if not cls.path_exists(config_dir_path):
|
||||||
os.makedirs(config_dir_path)
|
os.makedirs(config_dir_path)
|
||||||
with open(config_path, "wb") as config_file:
|
with open(config_path, "w") as config_file:
|
||||||
config_bytes, config = obj.generate_config(
|
config_str, config = obj.generate_config(
|
||||||
config_dir_path=config_dir_path,
|
config_dir_path=config_dir_path,
|
||||||
server_name=server_name,
|
server_name=server_name,
|
||||||
report_stats=(config_args.report_stats == "yes"),
|
report_stats=(config_args.report_stats == "yes"),
|
||||||
is_generating_file=True
|
is_generating_file=True
|
||||||
)
|
)
|
||||||
obj.invoke_all("generate_files", config)
|
obj.invoke_all("generate_files", config)
|
||||||
config_file.write(config_bytes)
|
config_file.write(config_str)
|
||||||
print((
|
print((
|
||||||
"A config file has been generated in %r for server name"
|
"A config file has been generated in %r for server name"
|
||||||
" %r with corresponding SSL keys and self-signed"
|
" %r with corresponding SSL keys and self-signed"
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ from ._base import Config, ConfigError
|
|||||||
from synapse.appservice import ApplicationService
|
from synapse.appservice import ApplicationService
|
||||||
from synapse.types import UserID
|
from synapse.types import UserID
|
||||||
|
|
||||||
import urllib
|
|
||||||
import yaml
|
import yaml
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from six import string_types
|
from six import string_types
|
||||||
|
from six.moves.urllib import parse as urlparse
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ def _load_appservice(hostname, as_info, config_filename):
|
|||||||
)
|
)
|
||||||
|
|
||||||
localpart = as_info["sender_localpart"]
|
localpart = as_info["sender_localpart"]
|
||||||
if urllib.quote(localpart) != localpart:
|
if urlparse.quote(localpart) != localpart:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"sender_localpart needs characters which are not URL encoded."
|
"sender_localpart needs characters which are not URL encoded."
|
||||||
)
|
)
|
||||||
|
|||||||
79
synapse/config/consent_config.py
Normal file
79
synapse/config/consent_config.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2018 New Vector Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from ._base import Config
|
||||||
|
|
||||||
|
DEFAULT_CONFIG = """\
|
||||||
|
# User Consent configuration
|
||||||
|
#
|
||||||
|
# Parts of this section are required if enabling the 'consent' resource under
|
||||||
|
# 'listeners', in particular 'template_dir' and 'version'.
|
||||||
|
#
|
||||||
|
# 'template_dir' gives the location of the templates for the HTML forms.
|
||||||
|
# This directory should contain one subdirectory per language (eg, 'en', 'fr'),
|
||||||
|
# and each language directory should contain the policy document (named as
|
||||||
|
# '<version>.html') and a success page (success.html).
|
||||||
|
#
|
||||||
|
# 'version' specifies the 'current' version of the policy document. It defines
|
||||||
|
# the version to be served by the consent resource if there is no 'v'
|
||||||
|
# parameter.
|
||||||
|
#
|
||||||
|
# 'server_notice_content', if enabled, will send a user a "Server Notice"
|
||||||
|
# asking them to consent to the privacy policy. The 'server_notices' section
|
||||||
|
# must also be configured for this to work.
|
||||||
|
#
|
||||||
|
# 'block_events_error', if set, will block any attempts to send events
|
||||||
|
# until the user consents to the privacy policy. The value of the setting is
|
||||||
|
# used as the text of the error.
|
||||||
|
#
|
||||||
|
# user_consent:
|
||||||
|
# template_dir: res/templates/privacy
|
||||||
|
# version: 1.0
|
||||||
|
# server_notice_content:
|
||||||
|
# msgtype: m.text
|
||||||
|
# body: >-
|
||||||
|
# To continue using this homeserver you must review and agree to the
|
||||||
|
# terms and conditions at %(consent_uri)s
|
||||||
|
# block_events_error: >-
|
||||||
|
# To continue using this homeserver you must review and agree to the
|
||||||
|
# terms and conditions at %(consent_uri)s
|
||||||
|
#
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ConsentConfig(Config):
|
||||||
|
def __init__(self):
|
||||||
|
super(ConsentConfig, self).__init__()
|
||||||
|
|
||||||
|
self.user_consent_version = None
|
||||||
|
self.user_consent_template_dir = None
|
||||||
|
self.user_consent_server_notice_content = None
|
||||||
|
self.block_events_without_consent_error = None
|
||||||
|
|
||||||
|
def read_config(self, config):
|
||||||
|
consent_config = config.get("user_consent")
|
||||||
|
if consent_config is None:
|
||||||
|
return
|
||||||
|
self.user_consent_version = str(consent_config["version"])
|
||||||
|
self.user_consent_template_dir = consent_config["template_dir"]
|
||||||
|
self.user_consent_server_notice_content = consent_config.get(
|
||||||
|
"server_notice_content",
|
||||||
|
)
|
||||||
|
self.block_events_without_consent_error = consent_config.get(
|
||||||
|
"block_events_error",
|
||||||
|
)
|
||||||
|
|
||||||
|
def default_config(self, **kwargs):
|
||||||
|
return DEFAULT_CONFIG
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014-2016 OpenMarket Ltd
|
||||||
|
# Copyright 2018 New Vector Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -12,7 +13,6 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from .tls import TlsConfig
|
from .tls import TlsConfig
|
||||||
from .server import ServerConfig
|
from .server import ServerConfig
|
||||||
from .logger import LoggingConfig
|
from .logger import LoggingConfig
|
||||||
@@ -37,6 +37,8 @@ from .push import PushConfig
|
|||||||
from .spam_checker import SpamCheckerConfig
|
from .spam_checker import SpamCheckerConfig
|
||||||
from .groups import GroupsConfig
|
from .groups import GroupsConfig
|
||||||
from .user_directory import UserDirectoryConfig
|
from .user_directory import UserDirectoryConfig
|
||||||
|
from .consent_config import ConsentConfig
|
||||||
|
from .server_notices_config import ServerNoticesConfig
|
||||||
|
|
||||||
|
|
||||||
class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
|
class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
|
||||||
@@ -45,12 +47,15 @@ class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
|
|||||||
AppServiceConfig, KeyConfig, SAML2Config, CasConfig,
|
AppServiceConfig, KeyConfig, SAML2Config, CasConfig,
|
||||||
JWTConfig, PasswordConfig, EmailConfig,
|
JWTConfig, PasswordConfig, EmailConfig,
|
||||||
WorkerConfig, PasswordAuthProviderConfig, PushConfig,
|
WorkerConfig, PasswordAuthProviderConfig, PushConfig,
|
||||||
SpamCheckerConfig, GroupsConfig, UserDirectoryConfig,):
|
SpamCheckerConfig, GroupsConfig, UserDirectoryConfig,
|
||||||
|
ConsentConfig,
|
||||||
|
ServerNoticesConfig,
|
||||||
|
):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import sys
|
import sys
|
||||||
sys.stdout.write(
|
sys.stdout.write(
|
||||||
HomeServerConfig().generate_config(sys.argv[1], sys.argv[2])[0]
|
HomeServerConfig().generate_config(sys.argv[1], sys.argv[2], True)[0]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -59,14 +59,20 @@ class KeyConfig(Config):
|
|||||||
|
|
||||||
self.expire_access_token = config.get("expire_access_token", False)
|
self.expire_access_token = config.get("expire_access_token", False)
|
||||||
|
|
||||||
|
# a secret which is used to calculate HMACs for form values, to stop
|
||||||
|
# falsification of values
|
||||||
|
self.form_secret = config.get("form_secret", None)
|
||||||
|
|
||||||
def default_config(self, config_dir_path, server_name, is_generating_file=False,
|
def default_config(self, config_dir_path, server_name, is_generating_file=False,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
base_key_name = os.path.join(config_dir_path, server_name)
|
base_key_name = os.path.join(config_dir_path, server_name)
|
||||||
|
|
||||||
if is_generating_file:
|
if is_generating_file:
|
||||||
macaroon_secret_key = random_string_with_symbols(50)
|
macaroon_secret_key = random_string_with_symbols(50)
|
||||||
|
form_secret = '"%s"' % random_string_with_symbols(50)
|
||||||
else:
|
else:
|
||||||
macaroon_secret_key = None
|
macaroon_secret_key = None
|
||||||
|
form_secret = 'null'
|
||||||
|
|
||||||
return """\
|
return """\
|
||||||
macaroon_secret_key: "%(macaroon_secret_key)s"
|
macaroon_secret_key: "%(macaroon_secret_key)s"
|
||||||
@@ -74,6 +80,10 @@ class KeyConfig(Config):
|
|||||||
# Used to enable access token expiration.
|
# Used to enable access token expiration.
|
||||||
expire_access_token: False
|
expire_access_token: False
|
||||||
|
|
||||||
|
# a secret which is used to calculate HMACs for form values, to stop
|
||||||
|
# falsification of values
|
||||||
|
form_secret: %(form_secret)s
|
||||||
|
|
||||||
## Signing Keys ##
|
## Signing Keys ##
|
||||||
|
|
||||||
# Path to the signing key to sign messages with
|
# Path to the signing key to sign messages with
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ class LoggingConfig(Config):
|
|||||||
log_config = config.get("log_config")
|
log_config = config.get("log_config")
|
||||||
if log_config and not os.path.exists(log_config):
|
if log_config and not os.path.exists(log_config):
|
||||||
log_file = self.abspath("homeserver.log")
|
log_file = self.abspath("homeserver.log")
|
||||||
with open(log_config, "wb") as log_config_file:
|
with open(log_config, "w") as log_config_file:
|
||||||
log_config_file.write(
|
log_config_file.write(
|
||||||
DEFAULT_LOG_CONFIG.substitute(log_file=log_file)
|
DEFAULT_LOG_CONFIG.substitute(log_file=log_file)
|
||||||
)
|
)
|
||||||
|
|||||||
86
synapse/config/server_notices_config.py
Normal file
86
synapse/config/server_notices_config.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2018 New Vector Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
from ._base import Config
|
||||||
|
from synapse.types import UserID
|
||||||
|
|
||||||
|
DEFAULT_CONFIG = """\
|
||||||
|
# Server Notices room configuration
|
||||||
|
#
|
||||||
|
# Uncomment this section to enable a room which can be used to send notices
|
||||||
|
# from the server to users. It is a special room which cannot be left; notices
|
||||||
|
# come from a special "notices" user id.
|
||||||
|
#
|
||||||
|
# If you uncomment this section, you *must* define the system_mxid_localpart
|
||||||
|
# setting, which defines the id of the user which will be used to send the
|
||||||
|
# notices.
|
||||||
|
#
|
||||||
|
# It's also possible to override the room name, the display name of the
|
||||||
|
# "notices" user, and the avatar for the user.
|
||||||
|
#
|
||||||
|
# server_notices:
|
||||||
|
# system_mxid_localpart: notices
|
||||||
|
# system_mxid_display_name: "Server Notices"
|
||||||
|
# system_mxid_avatar_url: "mxc://server.com/oumMVlgDnLYFaPVkExemNVVZ"
|
||||||
|
# room_name: "Server Notices"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ServerNoticesConfig(Config):
|
||||||
|
"""Configuration for the server notices room.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
server_notices_mxid (str|None):
|
||||||
|
The MXID to use for server notices.
|
||||||
|
None if server notices are not enabled.
|
||||||
|
|
||||||
|
server_notices_mxid_display_name (str|None):
|
||||||
|
The display name to use for the server notices user.
|
||||||
|
None if server notices are not enabled.
|
||||||
|
|
||||||
|
server_notices_mxid_avatar_url (str|None):
|
||||||
|
The display name to use for the server notices user.
|
||||||
|
None if server notices are not enabled.
|
||||||
|
|
||||||
|
server_notices_room_name (str|None):
|
||||||
|
The name to use for the server notices room.
|
||||||
|
None if server notices are not enabled.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
super(ServerNoticesConfig, self).__init__()
|
||||||
|
self.server_notices_mxid = None
|
||||||
|
self.server_notices_mxid_display_name = None
|
||||||
|
self.server_notices_mxid_avatar_url = None
|
||||||
|
self.server_notices_room_name = None
|
||||||
|
|
||||||
|
def read_config(self, config):
|
||||||
|
c = config.get("server_notices")
|
||||||
|
if c is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
mxid_localpart = c['system_mxid_localpart']
|
||||||
|
self.server_notices_mxid = UserID(
|
||||||
|
mxid_localpart, self.server_name,
|
||||||
|
).to_string()
|
||||||
|
self.server_notices_mxid_display_name = c.get(
|
||||||
|
'system_mxid_display_name', None,
|
||||||
|
)
|
||||||
|
self.server_notices_mxid_avatar_url = c.get(
|
||||||
|
'system_mxid_avatar_url', None,
|
||||||
|
)
|
||||||
|
# todo: i18n
|
||||||
|
self.server_notices_room_name = c.get('room_name', "Server Notices")
|
||||||
|
|
||||||
|
def default_config(self, **kwargs):
|
||||||
|
return DEFAULT_CONFIG
|
||||||
@@ -133,7 +133,7 @@ class TlsConfig(Config):
|
|||||||
tls_dh_params_path = config["tls_dh_params_path"]
|
tls_dh_params_path = config["tls_dh_params_path"]
|
||||||
|
|
||||||
if not self.path_exists(tls_private_key_path):
|
if not self.path_exists(tls_private_key_path):
|
||||||
with open(tls_private_key_path, "w") as private_key_file:
|
with open(tls_private_key_path, "wb") as private_key_file:
|
||||||
tls_private_key = crypto.PKey()
|
tls_private_key = crypto.PKey()
|
||||||
tls_private_key.generate_key(crypto.TYPE_RSA, 2048)
|
tls_private_key.generate_key(crypto.TYPE_RSA, 2048)
|
||||||
private_key_pem = crypto.dump_privatekey(
|
private_key_pem = crypto.dump_privatekey(
|
||||||
@@ -148,7 +148,7 @@ class TlsConfig(Config):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not self.path_exists(tls_certificate_path):
|
if not self.path_exists(tls_certificate_path):
|
||||||
with open(tls_certificate_path, "w") as certificate_file:
|
with open(tls_certificate_path, "wb") as certificate_file:
|
||||||
cert = crypto.X509()
|
cert = crypto.X509()
|
||||||
subject = cert.get_subject()
|
subject = cert.get_subject()
|
||||||
subject.CN = config["server_name"]
|
subject.CN = config["server_name"]
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from twisted.internet import ssl
|
from twisted.internet import ssl
|
||||||
from OpenSSL import SSL
|
from OpenSSL import SSL, crypto
|
||||||
from twisted.internet._sslverify import _OpenSSLECCurve, _defaultCurveName
|
from twisted.internet._sslverify import _defaultCurveName
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@@ -32,8 +32,9 @@ class ServerContextFactory(ssl.ContextFactory):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def configure_context(context, config):
|
def configure_context(context, config):
|
||||||
try:
|
try:
|
||||||
_ecCurve = _OpenSSLECCurve(_defaultCurveName)
|
_ecCurve = crypto.get_elliptic_curve(_defaultCurveName)
|
||||||
_ecCurve.addECKeyToContext(context)
|
context.set_tmp_ecdh(_ecCurve)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Failed to enable elliptic curve for TLS")
|
logger.exception("Failed to enable elliptic curve for TLS")
|
||||||
context.set_options(SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3)
|
context.set_options(SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3)
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ from synapse.api.errors import SynapseError, Codes
|
|||||||
from synapse.util import unwrapFirstError, logcontext
|
from synapse.util import unwrapFirstError, logcontext
|
||||||
from synapse.util.logcontext import (
|
from synapse.util.logcontext import (
|
||||||
PreserveLoggingContext,
|
PreserveLoggingContext,
|
||||||
preserve_fn
|
preserve_fn,
|
||||||
|
run_in_background,
|
||||||
)
|
)
|
||||||
from synapse.util.metrics import Measure
|
from synapse.util.metrics import Measure
|
||||||
|
|
||||||
@@ -127,7 +128,7 @@ class Keyring(object):
|
|||||||
|
|
||||||
verify_requests.append(verify_request)
|
verify_requests.append(verify_request)
|
||||||
|
|
||||||
preserve_fn(self._start_key_lookups)(verify_requests)
|
run_in_background(self._start_key_lookups, verify_requests)
|
||||||
|
|
||||||
# Pass those keys to handle_key_deferred so that the json object
|
# Pass those keys to handle_key_deferred so that the json object
|
||||||
# signatures can be verified
|
# signatures can be verified
|
||||||
@@ -146,53 +147,56 @@ class Keyring(object):
|
|||||||
verify_requests (List[VerifyKeyRequest]):
|
verify_requests (List[VerifyKeyRequest]):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# create a deferred for each server we're going to look up the keys
|
try:
|
||||||
# for; we'll resolve them once we have completed our lookups.
|
# create a deferred for each server we're going to look up the keys
|
||||||
# These will be passed into wait_for_previous_lookups to block
|
# for; we'll resolve them once we have completed our lookups.
|
||||||
# any other lookups until we have finished.
|
# These will be passed into wait_for_previous_lookups to block
|
||||||
# The deferreds are called with no logcontext.
|
# any other lookups until we have finished.
|
||||||
server_to_deferred = {
|
# The deferreds are called with no logcontext.
|
||||||
rq.server_name: defer.Deferred()
|
server_to_deferred = {
|
||||||
for rq in verify_requests
|
rq.server_name: defer.Deferred()
|
||||||
}
|
for rq in verify_requests
|
||||||
|
}
|
||||||
|
|
||||||
# We want to wait for any previous lookups to complete before
|
# We want to wait for any previous lookups to complete before
|
||||||
# proceeding.
|
# proceeding.
|
||||||
yield self.wait_for_previous_lookups(
|
yield self.wait_for_previous_lookups(
|
||||||
[rq.server_name for rq in verify_requests],
|
[rq.server_name for rq in verify_requests],
|
||||||
server_to_deferred,
|
server_to_deferred,
|
||||||
)
|
|
||||||
|
|
||||||
# Actually start fetching keys.
|
|
||||||
self._get_server_verify_keys(verify_requests)
|
|
||||||
|
|
||||||
# When we've finished fetching all the keys for a given server_name,
|
|
||||||
# resolve the deferred passed to `wait_for_previous_lookups` so that
|
|
||||||
# any lookups waiting will proceed.
|
|
||||||
#
|
|
||||||
# map from server name to a set of request ids
|
|
||||||
server_to_request_ids = {}
|
|
||||||
|
|
||||||
for verify_request in verify_requests:
|
|
||||||
server_name = verify_request.server_name
|
|
||||||
request_id = id(verify_request)
|
|
||||||
server_to_request_ids.setdefault(server_name, set()).add(request_id)
|
|
||||||
|
|
||||||
def remove_deferreds(res, verify_request):
|
|
||||||
server_name = verify_request.server_name
|
|
||||||
request_id = id(verify_request)
|
|
||||||
server_to_request_ids[server_name].discard(request_id)
|
|
||||||
if not server_to_request_ids[server_name]:
|
|
||||||
d = server_to_deferred.pop(server_name, None)
|
|
||||||
if d:
|
|
||||||
d.callback(None)
|
|
||||||
return res
|
|
||||||
|
|
||||||
for verify_request in verify_requests:
|
|
||||||
verify_request.deferred.addBoth(
|
|
||||||
remove_deferreds, verify_request,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Actually start fetching keys.
|
||||||
|
self._get_server_verify_keys(verify_requests)
|
||||||
|
|
||||||
|
# When we've finished fetching all the keys for a given server_name,
|
||||||
|
# resolve the deferred passed to `wait_for_previous_lookups` so that
|
||||||
|
# any lookups waiting will proceed.
|
||||||
|
#
|
||||||
|
# map from server name to a set of request ids
|
||||||
|
server_to_request_ids = {}
|
||||||
|
|
||||||
|
for verify_request in verify_requests:
|
||||||
|
server_name = verify_request.server_name
|
||||||
|
request_id = id(verify_request)
|
||||||
|
server_to_request_ids.setdefault(server_name, set()).add(request_id)
|
||||||
|
|
||||||
|
def remove_deferreds(res, verify_request):
|
||||||
|
server_name = verify_request.server_name
|
||||||
|
request_id = id(verify_request)
|
||||||
|
server_to_request_ids[server_name].discard(request_id)
|
||||||
|
if not server_to_request_ids[server_name]:
|
||||||
|
d = server_to_deferred.pop(server_name, None)
|
||||||
|
if d:
|
||||||
|
d.callback(None)
|
||||||
|
return res
|
||||||
|
|
||||||
|
for verify_request in verify_requests:
|
||||||
|
verify_request.deferred.addBoth(
|
||||||
|
remove_deferreds, verify_request,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error starting key lookups")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def wait_for_previous_lookups(self, server_names, server_to_deferred):
|
def wait_for_previous_lookups(self, server_names, server_to_deferred):
|
||||||
"""Waits for any previous key lookups for the given servers to finish.
|
"""Waits for any previous key lookups for the given servers to finish.
|
||||||
@@ -313,7 +317,7 @@ class Keyring(object):
|
|||||||
if not verify_request.deferred.called:
|
if not verify_request.deferred.called:
|
||||||
verify_request.deferred.errback(err)
|
verify_request.deferred.errback(err)
|
||||||
|
|
||||||
preserve_fn(do_iterations)().addErrback(on_err)
|
run_in_background(do_iterations).addErrback(on_err)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_keys_from_store(self, server_name_and_key_ids):
|
def get_keys_from_store(self, server_name_and_key_ids):
|
||||||
@@ -329,8 +333,9 @@ class Keyring(object):
|
|||||||
"""
|
"""
|
||||||
res = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
res = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
||||||
[
|
[
|
||||||
preserve_fn(self.store.get_server_verify_keys)(
|
run_in_background(
|
||||||
server_name, key_ids
|
self.store.get_server_verify_keys,
|
||||||
|
server_name, key_ids,
|
||||||
).addCallback(lambda ks, server: (server, ks), server_name)
|
).addCallback(lambda ks, server: (server, ks), server_name)
|
||||||
for server_name, key_ids in server_name_and_key_ids
|
for server_name, key_ids in server_name_and_key_ids
|
||||||
],
|
],
|
||||||
@@ -352,13 +357,13 @@ class Keyring(object):
|
|||||||
logger.exception(
|
logger.exception(
|
||||||
"Unable to get key from %r: %s %s",
|
"Unable to get key from %r: %s %s",
|
||||||
perspective_name,
|
perspective_name,
|
||||||
type(e).__name__, str(e.message),
|
type(e).__name__, str(e),
|
||||||
)
|
)
|
||||||
defer.returnValue({})
|
defer.returnValue({})
|
||||||
|
|
||||||
results = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
results = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
||||||
[
|
[
|
||||||
preserve_fn(get_key)(p_name, p_keys)
|
run_in_background(get_key, p_name, p_keys)
|
||||||
for p_name, p_keys in self.perspective_servers.items()
|
for p_name, p_keys in self.perspective_servers.items()
|
||||||
],
|
],
|
||||||
consumeErrors=True,
|
consumeErrors=True,
|
||||||
@@ -384,7 +389,7 @@ class Keyring(object):
|
|||||||
logger.info(
|
logger.info(
|
||||||
"Unable to get key %r for %r directly: %s %s",
|
"Unable to get key %r for %r directly: %s %s",
|
||||||
key_ids, server_name,
|
key_ids, server_name,
|
||||||
type(e).__name__, str(e.message),
|
type(e).__name__, str(e),
|
||||||
)
|
)
|
||||||
|
|
||||||
if not keys:
|
if not keys:
|
||||||
@@ -398,7 +403,7 @@ class Keyring(object):
|
|||||||
|
|
||||||
results = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
results = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
||||||
[
|
[
|
||||||
preserve_fn(get_key)(server_name, key_ids)
|
run_in_background(get_key, server_name, key_ids)
|
||||||
for server_name, key_ids in server_name_and_key_ids
|
for server_name, key_ids in server_name_and_key_ids
|
||||||
],
|
],
|
||||||
consumeErrors=True,
|
consumeErrors=True,
|
||||||
@@ -481,7 +486,8 @@ class Keyring(object):
|
|||||||
|
|
||||||
yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
||||||
[
|
[
|
||||||
preserve_fn(self.store_keys)(
|
run_in_background(
|
||||||
|
self.store_keys,
|
||||||
server_name=server_name,
|
server_name=server_name,
|
||||||
from_server=perspective_name,
|
from_server=perspective_name,
|
||||||
verify_keys=response_keys,
|
verify_keys=response_keys,
|
||||||
@@ -539,7 +545,8 @@ class Keyring(object):
|
|||||||
|
|
||||||
yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
||||||
[
|
[
|
||||||
preserve_fn(self.store_keys)(
|
run_in_background(
|
||||||
|
self.store_keys,
|
||||||
server_name=key_server_name,
|
server_name=key_server_name,
|
||||||
from_server=server_name,
|
from_server=server_name,
|
||||||
verify_keys=verify_keys,
|
verify_keys=verify_keys,
|
||||||
@@ -615,7 +622,8 @@ class Keyring(object):
|
|||||||
|
|
||||||
yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
||||||
[
|
[
|
||||||
preserve_fn(self.store.store_server_keys_json)(
|
run_in_background(
|
||||||
|
self.store.store_server_keys_json,
|
||||||
server_name=server_name,
|
server_name=server_name,
|
||||||
key_id=key_id,
|
key_id=key_id,
|
||||||
from_server=server_name,
|
from_server=server_name,
|
||||||
@@ -716,7 +724,8 @@ class Keyring(object):
|
|||||||
# TODO(markjh): Store whether the keys have expired.
|
# TODO(markjh): Store whether the keys have expired.
|
||||||
return logcontext.make_deferred_yieldable(defer.gatherResults(
|
return logcontext.make_deferred_yieldable(defer.gatherResults(
|
||||||
[
|
[
|
||||||
preserve_fn(self.store.store_server_verify_key)(
|
run_in_background(
|
||||||
|
self.store.store_server_verify_key,
|
||||||
server_name, server_name, key.time_added, key
|
server_name, server_name, key.time_added, key
|
||||||
)
|
)
|
||||||
for key_id, key in verify_keys.items()
|
for key_id, key in verify_keys.items()
|
||||||
@@ -734,7 +743,7 @@ def _handle_key_deferred(verify_request):
|
|||||||
except IOError as e:
|
except IOError as e:
|
||||||
logger.warn(
|
logger.warn(
|
||||||
"Got IOError when downloading keys for %s: %s %s",
|
"Got IOError when downloading keys for %s: %s %s",
|
||||||
server_name, type(e).__name__, str(e.message),
|
server_name, type(e).__name__, str(e),
|
||||||
)
|
)
|
||||||
raise SynapseError(
|
raise SynapseError(
|
||||||
502,
|
502,
|
||||||
@@ -744,7 +753,7 @@ def _handle_key_deferred(verify_request):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
"Got Exception when downloading keys for %s: %s %s",
|
"Got Exception when downloading keys for %s: %s %s",
|
||||||
server_name, type(e).__name__, str(e.message),
|
server_name, type(e).__name__, str(e),
|
||||||
)
|
)
|
||||||
raise SynapseError(
|
raise SynapseError(
|
||||||
401,
|
401,
|
||||||
|
|||||||
@@ -47,14 +47,26 @@ class _EventInternalMetadata(object):
|
|||||||
|
|
||||||
|
|
||||||
def _event_dict_property(key):
|
def _event_dict_property(key):
|
||||||
|
# We want to be able to use hasattr with the event dict properties.
|
||||||
|
# However, (on python3) hasattr expects AttributeError to be raised. Hence,
|
||||||
|
# we need to transform the KeyError into an AttributeError
|
||||||
def getter(self):
|
def getter(self):
|
||||||
return self._event_dict[key]
|
try:
|
||||||
|
return self._event_dict[key]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(key)
|
||||||
|
|
||||||
def setter(self, v):
|
def setter(self, v):
|
||||||
self._event_dict[key] = v
|
try:
|
||||||
|
self._event_dict[key] = v
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(key)
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
del self._event_dict[key]
|
try:
|
||||||
|
del self._event_dict[key]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(key)
|
||||||
|
|
||||||
return property(
|
return property(
|
||||||
getter,
|
getter,
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ from frozendict import frozendict
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from six import string_types
|
||||||
|
|
||||||
# Split strings on "." but not "\." This uses a negative lookbehind assertion for '\'
|
# Split strings on "." but not "\." This uses a negative lookbehind assertion for '\'
|
||||||
# (?<!stuff) matches if the current position in the string is not preceded
|
# (?<!stuff) matches if the current position in the string is not preceded
|
||||||
# by a match for 'stuff'.
|
# by a match for 'stuff'.
|
||||||
@@ -277,7 +279,7 @@ def serialize_event(e, time_now_ms, as_client_event=True,
|
|||||||
|
|
||||||
if only_event_fields:
|
if only_event_fields:
|
||||||
if (not isinstance(only_event_fields, list) or
|
if (not isinstance(only_event_fields, list) or
|
||||||
not all(isinstance(f, basestring) for f in only_event_fields)):
|
not all(isinstance(f, string_types) for f in only_event_fields)):
|
||||||
raise TypeError("only_event_fields must be a list of strings")
|
raise TypeError("only_event_fields must be a list of strings")
|
||||||
d = only_fields(d, only_event_fields)
|
d = only_fields(d, only_event_fields)
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ from synapse.types import EventID, RoomID, UserID
|
|||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
from synapse.api.constants import EventTypes, Membership
|
from synapse.api.constants import EventTypes, Membership
|
||||||
|
|
||||||
|
from six import string_types
|
||||||
|
|
||||||
|
|
||||||
class EventValidator(object):
|
class EventValidator(object):
|
||||||
|
|
||||||
@@ -49,7 +51,7 @@ class EventValidator(object):
|
|||||||
strings.append("state_key")
|
strings.append("state_key")
|
||||||
|
|
||||||
for s in strings:
|
for s in strings:
|
||||||
if not isinstance(getattr(event, s), basestring):
|
if not isinstance(getattr(event, s), string_types):
|
||||||
raise SynapseError(400, "Not '%s' a string type" % (s,))
|
raise SynapseError(400, "Not '%s' a string type" % (s,))
|
||||||
|
|
||||||
if event.type == EventTypes.Member:
|
if event.type == EventTypes.Member:
|
||||||
@@ -88,5 +90,5 @@ class EventValidator(object):
|
|||||||
for s in keys:
|
for s in keys:
|
||||||
if s not in d:
|
if s not in d:
|
||||||
raise SynapseError(400, "'%s' not in content" % (s,))
|
raise SynapseError(400, "'%s' not in content" % (s,))
|
||||||
if not isinstance(d[s], basestring):
|
if not isinstance(d[s], string_types):
|
||||||
raise SynapseError(400, "Not '%s' a string type" % (s,))
|
raise SynapseError(400, "Not '%s' a string type" % (s,))
|
||||||
|
|||||||
@@ -14,7 +14,10 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from synapse.api.errors import SynapseError
|
import six
|
||||||
|
|
||||||
|
from synapse.api.constants import MAX_DEPTH
|
||||||
|
from synapse.api.errors import SynapseError, Codes
|
||||||
from synapse.crypto.event_signing import check_event_content_hash
|
from synapse.crypto.event_signing import check_event_content_hash
|
||||||
from synapse.events import FrozenEvent
|
from synapse.events import FrozenEvent
|
||||||
from synapse.events.utils import prune_event
|
from synapse.events.utils import prune_event
|
||||||
@@ -190,11 +193,23 @@ def event_from_pdu_json(pdu_json, outlier=False):
|
|||||||
FrozenEvent
|
FrozenEvent
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
SynapseError: if the pdu is missing required fields
|
SynapseError: if the pdu is missing required fields or is otherwise
|
||||||
|
not a valid matrix event
|
||||||
"""
|
"""
|
||||||
# we could probably enforce a bunch of other fields here (room_id, sender,
|
# we could probably enforce a bunch of other fields here (room_id, sender,
|
||||||
# origin, etc etc)
|
# origin, etc etc)
|
||||||
assert_params_in_request(pdu_json, ('event_id', 'type'))
|
assert_params_in_request(pdu_json, ('event_id', 'type', 'depth'))
|
||||||
|
|
||||||
|
depth = pdu_json['depth']
|
||||||
|
if not isinstance(depth, six.integer_types):
|
||||||
|
raise SynapseError(400, "Depth %r not an intger" % (depth, ),
|
||||||
|
Codes.BAD_JSON)
|
||||||
|
|
||||||
|
if depth < 0:
|
||||||
|
raise SynapseError(400, "Depth too small", Codes.BAD_JSON)
|
||||||
|
elif depth > MAX_DEPTH:
|
||||||
|
raise SynapseError(400, "Depth too large", Codes.BAD_JSON)
|
||||||
|
|
||||||
event = FrozenEvent(
|
event = FrozenEvent(
|
||||||
pdu_json
|
pdu_json
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import itertools
|
|||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
from six.moves import range
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.api.constants import Membership
|
from synapse.api.constants import Membership
|
||||||
@@ -33,7 +35,7 @@ from synapse.federation.federation_base import (
|
|||||||
import synapse.metrics
|
import synapse.metrics
|
||||||
from synapse.util import logcontext, unwrapFirstError
|
from synapse.util import logcontext, unwrapFirstError
|
||||||
from synapse.util.caches.expiringcache import ExpiringCache
|
from synapse.util.caches.expiringcache import ExpiringCache
|
||||||
from synapse.util.logcontext import make_deferred_yieldable, preserve_fn
|
from synapse.util.logcontext import make_deferred_yieldable, run_in_background
|
||||||
from synapse.util.logutils import log_function
|
from synapse.util.logutils import log_function
|
||||||
from synapse.util.retryutils import NotRetryingDestination
|
from synapse.util.retryutils import NotRetryingDestination
|
||||||
|
|
||||||
@@ -394,7 +396,7 @@ class FederationClient(FederationBase):
|
|||||||
seen_events = yield self.store.get_events(event_ids, allow_rejected=True)
|
seen_events = yield self.store.get_events(event_ids, allow_rejected=True)
|
||||||
signed_events = seen_events.values()
|
signed_events = seen_events.values()
|
||||||
else:
|
else:
|
||||||
seen_events = yield self.store.have_events(event_ids)
|
seen_events = yield self.store.have_seen_events(event_ids)
|
||||||
signed_events = []
|
signed_events = []
|
||||||
|
|
||||||
failed_to_fetch = set()
|
failed_to_fetch = set()
|
||||||
@@ -413,11 +415,12 @@ class FederationClient(FederationBase):
|
|||||||
|
|
||||||
batch_size = 20
|
batch_size = 20
|
||||||
missing_events = list(missing_events)
|
missing_events = list(missing_events)
|
||||||
for i in xrange(0, len(missing_events), batch_size):
|
for i in range(0, len(missing_events), batch_size):
|
||||||
batch = set(missing_events[i:i + batch_size])
|
batch = set(missing_events[i:i + batch_size])
|
||||||
|
|
||||||
deferreds = [
|
deferreds = [
|
||||||
preserve_fn(self.get_pdu)(
|
run_in_background(
|
||||||
|
self.get_pdu,
|
||||||
destinations=random_server_list(),
|
destinations=random_server_list(),
|
||||||
event_id=e_id,
|
event_id=e_id,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2015, 2016 OpenMarket Ltd
|
# Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
# Copyright 2018 New Vector Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -30,9 +31,10 @@ import synapse.metrics
|
|||||||
from synapse.types import get_domain_from_id
|
from synapse.types import get_domain_from_id
|
||||||
from synapse.util import async
|
from synapse.util import async
|
||||||
from synapse.util.caches.response_cache import ResponseCache
|
from synapse.util.caches.response_cache import ResponseCache
|
||||||
from synapse.util.logcontext import make_deferred_yieldable, preserve_fn
|
|
||||||
from synapse.util.logutils import log_function
|
from synapse.util.logutils import log_function
|
||||||
|
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
# when processing incoming transactions, we try to handle multiple rooms in
|
# when processing incoming transactions, we try to handle multiple rooms in
|
||||||
# parallel, up to this limit.
|
# parallel, up to this limit.
|
||||||
TRANSACTION_CONCURRENCY_LIMIT = 10
|
TRANSACTION_CONCURRENCY_LIMIT = 10
|
||||||
@@ -212,16 +214,17 @@ class FederationServer(FederationBase):
|
|||||||
if not in_room:
|
if not in_room:
|
||||||
raise AuthError(403, "Host not in room.")
|
raise AuthError(403, "Host not in room.")
|
||||||
|
|
||||||
result = self._state_resp_cache.get((room_id, event_id))
|
# we grab the linearizer to protect ourselves from servers which hammer
|
||||||
if not result:
|
# us. In theory we might already have the response to this query
|
||||||
with (yield self._server_linearizer.queue((origin, room_id))):
|
# in the cache so we could return it without waiting for the linearizer
|
||||||
d = self._state_resp_cache.set(
|
# - but that's non-trivial to get right, and anyway somewhat defeats
|
||||||
(room_id, event_id),
|
# the point of the linearizer.
|
||||||
preserve_fn(self._on_context_state_request_compute)(room_id, event_id)
|
with (yield self._server_linearizer.queue((origin, room_id))):
|
||||||
)
|
resp = yield self._state_resp_cache.wrap(
|
||||||
resp = yield make_deferred_yieldable(d)
|
(room_id, event_id),
|
||||||
else:
|
self._on_context_state_request_compute,
|
||||||
resp = yield make_deferred_yieldable(result)
|
room_id, event_id,
|
||||||
|
)
|
||||||
|
|
||||||
defer.returnValue((200, resp))
|
defer.returnValue((200, resp))
|
||||||
|
|
||||||
@@ -425,9 +428,9 @@ class FederationServer(FederationBase):
|
|||||||
"Claimed one-time-keys: %s",
|
"Claimed one-time-keys: %s",
|
||||||
",".join((
|
",".join((
|
||||||
"%s for %s:%s" % (key_id, user_id, device_id)
|
"%s for %s:%s" % (key_id, user_id, device_id)
|
||||||
for user_id, user_keys in json_result.iteritems()
|
for user_id, user_keys in iteritems(json_result)
|
||||||
for device_id, device_keys in user_keys.iteritems()
|
for device_id, device_keys in iteritems(user_keys)
|
||||||
for key_id, _ in device_keys.iteritems()
|
for key_id, _ in iteritems(device_keys)
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -494,13 +497,33 @@ class FederationServer(FederationBase):
|
|||||||
def _handle_received_pdu(self, origin, pdu):
|
def _handle_received_pdu(self, origin, pdu):
|
||||||
""" Process a PDU received in a federation /send/ transaction.
|
""" Process a PDU received in a federation /send/ transaction.
|
||||||
|
|
||||||
|
If the event is invalid, then this method throws a FederationError.
|
||||||
|
(The error will then be logged and sent back to the sender (which
|
||||||
|
probably won't do anything with it), and other events in the
|
||||||
|
transaction will be processed as normal).
|
||||||
|
|
||||||
|
It is likely that we'll then receive other events which refer to
|
||||||
|
this rejected_event in their prev_events, etc. When that happens,
|
||||||
|
we'll attempt to fetch the rejected event again, which will presumably
|
||||||
|
fail, so those second-generation events will also get rejected.
|
||||||
|
|
||||||
|
Eventually, we get to the point where there are more than 10 events
|
||||||
|
between any new events and the original rejected event. Since we
|
||||||
|
only try to backfill 10 events deep on received pdu, we then accept the
|
||||||
|
new event, possibly introducing a discontinuity in the DAG, with new
|
||||||
|
forward extremities, so normal service is approximately returned,
|
||||||
|
until we try to backfill across the discontinuity.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
origin (str): server which sent the pdu
|
origin (str): server which sent the pdu
|
||||||
pdu (FrozenEvent): received pdu
|
pdu (FrozenEvent): received pdu
|
||||||
|
|
||||||
Returns (Deferred): completes with None
|
Returns (Deferred): completes with None
|
||||||
Raises: FederationError if the signatures / hash do not match
|
|
||||||
"""
|
Raises: FederationError if the signatures / hash do not match, or
|
||||||
|
if the event was unacceptable for any other reason (eg, too large,
|
||||||
|
too many prev_events, couldn't find the prev_events)
|
||||||
|
"""
|
||||||
# check that it's actually being sent from a valid destination to
|
# check that it's actually being sent from a valid destination to
|
||||||
# workaround bug #1753 in 0.18.5 and 0.18.6
|
# workaround bug #1753 in 0.18.5 and 0.18.6
|
||||||
if origin != get_domain_from_id(pdu.event_id):
|
if origin != get_domain_from_id(pdu.event_id):
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ from collections import namedtuple
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from six import itervalues, iteritems
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -122,7 +124,7 @@ class FederationRemoteSendQueue(object):
|
|||||||
|
|
||||||
user_ids = set(
|
user_ids = set(
|
||||||
user_id
|
user_id
|
||||||
for uids in self.presence_changed.itervalues()
|
for uids in itervalues(self.presence_changed)
|
||||||
for user_id in uids
|
for user_id in uids
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -276,7 +278,7 @@ class FederationRemoteSendQueue(object):
|
|||||||
# stream position.
|
# stream position.
|
||||||
keyed_edus = {self.keyed_edu_changed[k]: k for k in keys[i:j]}
|
keyed_edus = {self.keyed_edu_changed[k]: k for k in keys[i:j]}
|
||||||
|
|
||||||
for ((destination, edu_key), pos) in keyed_edus.iteritems():
|
for ((destination, edu_key), pos) in iteritems(keyed_edus):
|
||||||
rows.append((pos, KeyedEduRow(
|
rows.append((pos, KeyedEduRow(
|
||||||
key=edu_key,
|
key=edu_key,
|
||||||
edu=self.keyed_edu[(destination, edu_key)],
|
edu=self.keyed_edu[(destination, edu_key)],
|
||||||
@@ -309,7 +311,7 @@ class FederationRemoteSendQueue(object):
|
|||||||
j = keys.bisect_right(to_token) + 1
|
j = keys.bisect_right(to_token) + 1
|
||||||
device_messages = {self.device_messages[k]: k for k in keys[i:j]}
|
device_messages = {self.device_messages[k]: k for k in keys[i:j]}
|
||||||
|
|
||||||
for (destination, pos) in device_messages.iteritems():
|
for (destination, pos) in iteritems(device_messages):
|
||||||
rows.append((pos, DeviceRow(
|
rows.append((pos, DeviceRow(
|
||||||
destination=destination,
|
destination=destination,
|
||||||
)))
|
)))
|
||||||
@@ -528,19 +530,19 @@ def process_rows_for_federation(transaction_queue, rows):
|
|||||||
if buff.presence:
|
if buff.presence:
|
||||||
transaction_queue.send_presence(buff.presence)
|
transaction_queue.send_presence(buff.presence)
|
||||||
|
|
||||||
for destination, edu_map in buff.keyed_edus.iteritems():
|
for destination, edu_map in iteritems(buff.keyed_edus):
|
||||||
for key, edu in edu_map.items():
|
for key, edu in edu_map.items():
|
||||||
transaction_queue.send_edu(
|
transaction_queue.send_edu(
|
||||||
edu.destination, edu.edu_type, edu.content, key=key,
|
edu.destination, edu.edu_type, edu.content, key=key,
|
||||||
)
|
)
|
||||||
|
|
||||||
for destination, edu_list in buff.edus.iteritems():
|
for destination, edu_list in iteritems(buff.edus):
|
||||||
for edu in edu_list:
|
for edu in edu_list:
|
||||||
transaction_queue.send_edu(
|
transaction_queue.send_edu(
|
||||||
edu.destination, edu.edu_type, edu.content, key=None,
|
edu.destination, edu.edu_type, edu.content, key=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
for destination, failure_list in buff.failures.iteritems():
|
for destination, failure_list in iteritems(buff.failures):
|
||||||
for failure in failure_list:
|
for failure in failure_list:
|
||||||
transaction_queue.send_failure(destination, failure)
|
transaction_queue.send_failure(destination, failure)
|
||||||
|
|
||||||
|
|||||||
@@ -323,6 +323,8 @@ class TransactionQueue(object):
|
|||||||
break
|
break
|
||||||
|
|
||||||
yield self._process_presence_inner(states_map.values())
|
yield self._process_presence_inner(states_map.values())
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error sending presence states to servers")
|
||||||
finally:
|
finally:
|
||||||
self._processing_pending_presence = False
|
self._processing_pending_presence = False
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from synapse.http.servlet import (
|
|||||||
)
|
)
|
||||||
from synapse.util.ratelimitutils import FederationRateLimiter
|
from synapse.util.ratelimitutils import FederationRateLimiter
|
||||||
from synapse.util.versionstring import get_version_string
|
from synapse.util.versionstring import get_version_string
|
||||||
from synapse.util.logcontext import preserve_fn
|
from synapse.util.logcontext import run_in_background
|
||||||
from synapse.types import ThirdPartyInstanceID, get_domain_from_id
|
from synapse.types import ThirdPartyInstanceID, get_domain_from_id
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
@@ -94,12 +94,6 @@ class Authenticator(object):
|
|||||||
"signatures": {},
|
"signatures": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
self.federation_domain_whitelist is not None and
|
|
||||||
self.server_name not in self.federation_domain_whitelist
|
|
||||||
):
|
|
||||||
raise FederationDeniedError(self.server_name)
|
|
||||||
|
|
||||||
if content is not None:
|
if content is not None:
|
||||||
json_request["content"] = content
|
json_request["content"] = content
|
||||||
|
|
||||||
@@ -138,6 +132,12 @@ class Authenticator(object):
|
|||||||
json_request["origin"] = origin
|
json_request["origin"] = origin
|
||||||
json_request["signatures"].setdefault(origin, {})[key] = sig
|
json_request["signatures"].setdefault(origin, {})[key] = sig
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.federation_domain_whitelist is not None and
|
||||||
|
origin not in self.federation_domain_whitelist
|
||||||
|
):
|
||||||
|
raise FederationDeniedError(origin)
|
||||||
|
|
||||||
if not json_request["signatures"]:
|
if not json_request["signatures"]:
|
||||||
raise NoAuthenticationError(
|
raise NoAuthenticationError(
|
||||||
401, "Missing Authorization headers", Codes.UNAUTHORIZED,
|
401, "Missing Authorization headers", Codes.UNAUTHORIZED,
|
||||||
@@ -152,11 +152,18 @@ class Authenticator(object):
|
|||||||
# alive
|
# alive
|
||||||
retry_timings = yield self.store.get_destination_retry_timings(origin)
|
retry_timings = yield self.store.get_destination_retry_timings(origin)
|
||||||
if retry_timings and retry_timings["retry_last_ts"]:
|
if retry_timings and retry_timings["retry_last_ts"]:
|
||||||
logger.info("Marking origin %r as up", origin)
|
run_in_background(self._reset_retry_timings, origin)
|
||||||
preserve_fn(self.store.set_destination_retry_timings)(origin, 0, 0)
|
|
||||||
|
|
||||||
defer.returnValue(origin)
|
defer.returnValue(origin)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _reset_retry_timings(self, origin):
|
||||||
|
try:
|
||||||
|
logger.info("Marking origin %r as up", origin)
|
||||||
|
yield self.store.set_destination_retry_timings(origin, 0, 0)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error resetting retry timings on %s", origin)
|
||||||
|
|
||||||
|
|
||||||
class BaseFederationServlet(object):
|
class BaseFederationServlet(object):
|
||||||
REQUIRE_AUTH = True
|
REQUIRE_AUTH = True
|
||||||
|
|||||||
@@ -74,8 +74,6 @@ class Transaction(JsonEncodedObject):
|
|||||||
"previous_ids",
|
"previous_ids",
|
||||||
"pdus",
|
"pdus",
|
||||||
"edus",
|
"edus",
|
||||||
"transaction_id",
|
|
||||||
"destination",
|
|
||||||
"pdu_failures",
|
"pdu_failures",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ from twisted.internet import defer
|
|||||||
|
|
||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
from synapse.types import get_domain_from_id
|
from synapse.types import get_domain_from_id
|
||||||
from synapse.util.logcontext import preserve_fn
|
from synapse.util.logcontext import run_in_background
|
||||||
|
|
||||||
from signedjson.sign import sign_json
|
from signedjson.sign import sign_json
|
||||||
|
|
||||||
@@ -165,31 +165,35 @@ class GroupAttestionRenewer(object):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _renew_attestation(group_id, user_id):
|
def _renew_attestation(group_id, user_id):
|
||||||
if not self.is_mine_id(group_id):
|
try:
|
||||||
destination = get_domain_from_id(group_id)
|
if not self.is_mine_id(group_id):
|
||||||
elif not self.is_mine_id(user_id):
|
destination = get_domain_from_id(group_id)
|
||||||
destination = get_domain_from_id(user_id)
|
elif not self.is_mine_id(user_id):
|
||||||
else:
|
destination = get_domain_from_id(user_id)
|
||||||
logger.warn(
|
else:
|
||||||
"Incorrectly trying to do attestations for user: %r in %r",
|
logger.warn(
|
||||||
user_id, group_id,
|
"Incorrectly trying to do attestations for user: %r in %r",
|
||||||
|
user_id, group_id,
|
||||||
|
)
|
||||||
|
yield self.store.remove_attestation_renewal(group_id, user_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
attestation = self.attestations.create_attestation(group_id, user_id)
|
||||||
|
|
||||||
|
yield self.transport_client.renew_group_attestation(
|
||||||
|
destination, group_id, user_id,
|
||||||
|
content={"attestation": attestation},
|
||||||
)
|
)
|
||||||
yield self.store.remove_attestation_renewal(group_id, user_id)
|
|
||||||
return
|
|
||||||
|
|
||||||
attestation = self.attestations.create_attestation(group_id, user_id)
|
yield self.store.update_attestation_renewal(
|
||||||
|
group_id, user_id, attestation
|
||||||
yield self.transport_client.renew_group_attestation(
|
)
|
||||||
destination, group_id, user_id,
|
except Exception:
|
||||||
content={"attestation": attestation},
|
logger.exception("Error renewing attestation of %r in %r",
|
||||||
)
|
user_id, group_id)
|
||||||
|
|
||||||
yield self.store.update_attestation_renewal(
|
|
||||||
group_id, user_id, attestation
|
|
||||||
)
|
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
group_id = row["group_id"]
|
group_id = row["group_id"]
|
||||||
user_id = row["user_id"]
|
user_id = row["user_id"]
|
||||||
|
|
||||||
preserve_fn(_renew_attestation)(group_id, user_id)
|
run_in_background(_renew_attestation, group_id, user_id)
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ from synapse.api.errors import SynapseError
|
|||||||
from synapse.types import GroupID, RoomID, UserID, get_domain_from_id
|
from synapse.types import GroupID, RoomID, UserID, get_domain_from_id
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from six import string_types
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -431,7 +433,7 @@ class GroupsServerHandler(object):
|
|||||||
"long_description"):
|
"long_description"):
|
||||||
if keyname in content:
|
if keyname in content:
|
||||||
value = content[keyname]
|
value = content[keyname]
|
||||||
if not isinstance(value, basestring):
|
if not isinstance(value, string_types):
|
||||||
raise SynapseError(400, "%r value is not a string" % (keyname,))
|
raise SynapseError(400, "%r value is not a string" % (keyname,))
|
||||||
profile[keyname] = value
|
profile[keyname] = value
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from .register import RegistrationHandler
|
from .register import RegistrationHandler
|
||||||
from .room import (
|
from .room import RoomContextHandler
|
||||||
RoomCreationHandler, RoomContextHandler,
|
|
||||||
)
|
|
||||||
from .message import MessageHandler
|
from .message import MessageHandler
|
||||||
from .federation import FederationHandler
|
from .federation import FederationHandler
|
||||||
from .directory import DirectoryHandler
|
from .directory import DirectoryHandler
|
||||||
@@ -47,7 +45,6 @@ class Handlers(object):
|
|||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
self.registration_handler = RegistrationHandler(hs)
|
self.registration_handler = RegistrationHandler(hs)
|
||||||
self.message_handler = MessageHandler(hs)
|
self.message_handler = MessageHandler(hs)
|
||||||
self.room_creation_handler = RoomCreationHandler(hs)
|
|
||||||
self.federation_handler = FederationHandler(hs)
|
self.federation_handler = FederationHandler(hs)
|
||||||
self.directory_handler = DirectoryHandler(hs)
|
self.directory_handler = DirectoryHandler(hs)
|
||||||
self.admin_handler = AdminHandler(hs)
|
self.admin_handler = AdminHandler(hs)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import synapse
|
|||||||
from synapse.api.constants import EventTypes
|
from synapse.api.constants import EventTypes
|
||||||
from synapse.util.metrics import Measure
|
from synapse.util.metrics import Measure
|
||||||
from synapse.util.logcontext import (
|
from synapse.util.logcontext import (
|
||||||
make_deferred_yieldable, preserve_fn, run_in_background,
|
make_deferred_yieldable, run_in_background,
|
||||||
)
|
)
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@@ -111,9 +111,7 @@ class ApplicationServicesHandler(object):
|
|||||||
|
|
||||||
# Fork off pushes to these services
|
# Fork off pushes to these services
|
||||||
for service in services:
|
for service in services:
|
||||||
preserve_fn(self.scheduler.submit_event_for_as)(
|
self.scheduler.submit_event_for_as(service, event)
|
||||||
service, event
|
|
||||||
)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def handle_room_events(events):
|
def handle_room_events(events):
|
||||||
@@ -198,7 +196,10 @@ class ApplicationServicesHandler(object):
|
|||||||
services = yield self._get_services_for_3pn(protocol)
|
services = yield self._get_services_for_3pn(protocol)
|
||||||
|
|
||||||
results = yield make_deferred_yieldable(defer.DeferredList([
|
results = yield make_deferred_yieldable(defer.DeferredList([
|
||||||
preserve_fn(self.appservice_api.query_3pe)(service, kind, protocol, fields)
|
run_in_background(
|
||||||
|
self.appservice_api.query_3pe,
|
||||||
|
service, kind, protocol, fields,
|
||||||
|
)
|
||||||
for service in services
|
for service in services
|
||||||
], consumeErrors=True))
|
], consumeErrors=True))
|
||||||
|
|
||||||
@@ -259,11 +260,15 @@ class ApplicationServicesHandler(object):
|
|||||||
event based on the service regex.
|
event based on the service regex.
|
||||||
"""
|
"""
|
||||||
services = self.store.get_app_services()
|
services = self.store.get_app_services()
|
||||||
interested_list = [
|
|
||||||
s for s in services if (
|
# we can't use a list comprehension here. Since python 3, list
|
||||||
yield s.is_interested(event, self.store)
|
# comprehensions use a generator internally. This means you can't yield
|
||||||
)
|
# inside of a list comprehension anymore.
|
||||||
]
|
interested_list = []
|
||||||
|
for s in services:
|
||||||
|
if (yield s.is_interested(event, self.store)):
|
||||||
|
interested_list.append(s)
|
||||||
|
|
||||||
defer.returnValue(interested_list)
|
defer.returnValue(interested_list)
|
||||||
|
|
||||||
def _get_services_for_user(self, user_id):
|
def _get_services_for_user(self, user_id):
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2017 New Vector Ltd
|
# Copyright 2017, 2018 New Vector Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -12,9 +12,11 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer, reactor
|
||||||
|
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
|
from synapse.types import UserID, create_requester
|
||||||
|
from synapse.util.logcontext import run_in_background
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@@ -27,6 +29,15 @@ class DeactivateAccountHandler(BaseHandler):
|
|||||||
super(DeactivateAccountHandler, self).__init__(hs)
|
super(DeactivateAccountHandler, self).__init__(hs)
|
||||||
self._auth_handler = hs.get_auth_handler()
|
self._auth_handler = hs.get_auth_handler()
|
||||||
self._device_handler = hs.get_device_handler()
|
self._device_handler = hs.get_device_handler()
|
||||||
|
self._room_member_handler = hs.get_room_member_handler()
|
||||||
|
self.user_directory_handler = hs.get_user_directory_handler()
|
||||||
|
|
||||||
|
# Flag that indicates whether the process to part users from rooms is running
|
||||||
|
self._user_parter_running = False
|
||||||
|
|
||||||
|
# Start the user parter loop so it can resume parting users from rooms where
|
||||||
|
# it left off (if it has work left to do).
|
||||||
|
reactor.callWhenRunning(self._start_user_parting)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def deactivate_account(self, user_id):
|
def deactivate_account(self, user_id):
|
||||||
@@ -50,3 +61,73 @@ class DeactivateAccountHandler(BaseHandler):
|
|||||||
|
|
||||||
yield self.store.user_delete_threepids(user_id)
|
yield self.store.user_delete_threepids(user_id)
|
||||||
yield self.store.user_set_password_hash(user_id, None)
|
yield self.store.user_set_password_hash(user_id, None)
|
||||||
|
|
||||||
|
# Add the user to a table of users pending deactivation (ie.
|
||||||
|
# removal from all the rooms they're a member of)
|
||||||
|
yield self.store.add_user_pending_deactivation(user_id)
|
||||||
|
|
||||||
|
# delete from user directory
|
||||||
|
yield self.user_directory_handler.handle_user_deactivated(user_id)
|
||||||
|
|
||||||
|
# Now start the process that goes through that list and
|
||||||
|
# parts users from rooms (if it isn't already running)
|
||||||
|
self._start_user_parting()
|
||||||
|
|
||||||
|
def _start_user_parting(self):
|
||||||
|
"""
|
||||||
|
Start the process that goes through the table of users
|
||||||
|
pending deactivation, if it isn't already running.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
if not self._user_parter_running:
|
||||||
|
run_in_background(self._user_parter_loop)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _user_parter_loop(self):
|
||||||
|
"""Loop that parts deactivated users from rooms
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self._user_parter_running = True
|
||||||
|
logger.info("Starting user parter")
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
user_id = yield self.store.get_user_pending_deactivation()
|
||||||
|
if user_id is None:
|
||||||
|
break
|
||||||
|
logger.info("User parter parting %r", user_id)
|
||||||
|
yield self._part_user(user_id)
|
||||||
|
yield self.store.del_user_pending_deactivation(user_id)
|
||||||
|
logger.info("User parter finished parting %r", user_id)
|
||||||
|
logger.info("User parter finished: stopping")
|
||||||
|
finally:
|
||||||
|
self._user_parter_running = False
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _part_user(self, user_id):
|
||||||
|
"""Causes the given user_id to leave all the rooms they're joined to
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
user = UserID.from_string(user_id)
|
||||||
|
|
||||||
|
rooms_for_user = yield self.store.get_rooms_for_user(user_id)
|
||||||
|
for room_id in rooms_for_user:
|
||||||
|
logger.info("User parter parting %r from %r", user_id, room_id)
|
||||||
|
try:
|
||||||
|
yield self._room_member_handler.update_membership(
|
||||||
|
create_requester(user),
|
||||||
|
user,
|
||||||
|
room_id,
|
||||||
|
"leave",
|
||||||
|
ratelimit=False,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception(
|
||||||
|
"Failed to part user %r from room %r: ignoring and continuing",
|
||||||
|
user_id, room_id,
|
||||||
|
)
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ from ._base import BaseHandler
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from six import itervalues, iteritems
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -318,7 +320,7 @@ class DeviceHandler(BaseHandler):
|
|||||||
# The user may have left the room
|
# The user may have left the room
|
||||||
# TODO: Check if they actually did or if we were just invited.
|
# TODO: Check if they actually did or if we were just invited.
|
||||||
if room_id not in room_ids:
|
if room_id not in room_ids:
|
||||||
for key, event_id in current_state_ids.iteritems():
|
for key, event_id in iteritems(current_state_ids):
|
||||||
etype, state_key = key
|
etype, state_key = key
|
||||||
if etype != EventTypes.Member:
|
if etype != EventTypes.Member:
|
||||||
continue
|
continue
|
||||||
@@ -338,7 +340,7 @@ class DeviceHandler(BaseHandler):
|
|||||||
# special-case for an empty prev state: include all members
|
# special-case for an empty prev state: include all members
|
||||||
# in the changed list
|
# in the changed list
|
||||||
if not event_ids:
|
if not event_ids:
|
||||||
for key, event_id in current_state_ids.iteritems():
|
for key, event_id in iteritems(current_state_ids):
|
||||||
etype, state_key = key
|
etype, state_key = key
|
||||||
if etype != EventTypes.Member:
|
if etype != EventTypes.Member:
|
||||||
continue
|
continue
|
||||||
@@ -354,10 +356,10 @@ class DeviceHandler(BaseHandler):
|
|||||||
|
|
||||||
# Check if we've joined the room? If so we just blindly add all the users to
|
# Check if we've joined the room? If so we just blindly add all the users to
|
||||||
# the "possibly changed" users.
|
# the "possibly changed" users.
|
||||||
for state_dict in prev_state_ids.itervalues():
|
for state_dict in itervalues(prev_state_ids):
|
||||||
member_event = state_dict.get((EventTypes.Member, user_id), None)
|
member_event = state_dict.get((EventTypes.Member, user_id), None)
|
||||||
if not member_event or member_event != current_member_id:
|
if not member_event or member_event != current_member_id:
|
||||||
for key, event_id in current_state_ids.iteritems():
|
for key, event_id in iteritems(current_state_ids):
|
||||||
etype, state_key = key
|
etype, state_key = key
|
||||||
if etype != EventTypes.Member:
|
if etype != EventTypes.Member:
|
||||||
continue
|
continue
|
||||||
@@ -367,14 +369,14 @@ class DeviceHandler(BaseHandler):
|
|||||||
# If there has been any change in membership, include them in the
|
# If there has been any change in membership, include them in the
|
||||||
# possibly changed list. We'll check if they are joined below,
|
# possibly changed list. We'll check if they are joined below,
|
||||||
# and we're not toooo worried about spuriously adding users.
|
# and we're not toooo worried about spuriously adding users.
|
||||||
for key, event_id in current_state_ids.iteritems():
|
for key, event_id in iteritems(current_state_ids):
|
||||||
etype, state_key = key
|
etype, state_key = key
|
||||||
if etype != EventTypes.Member:
|
if etype != EventTypes.Member:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# check if this member has changed since any of the extremities
|
# check if this member has changed since any of the extremities
|
||||||
# at the stream_ordering, and add them to the list if so.
|
# at the stream_ordering, and add them to the list if so.
|
||||||
for state_dict in prev_state_ids.itervalues():
|
for state_dict in itervalues(prev_state_ids):
|
||||||
prev_event_id = state_dict.get(key, None)
|
prev_event_id = state_dict.get(key, None)
|
||||||
if not prev_event_id or prev_event_id != event_id:
|
if not prev_event_id or prev_event_id != event_id:
|
||||||
if state_key != user_id:
|
if state_key != user_id:
|
||||||
|
|||||||
@@ -19,12 +19,13 @@ import logging
|
|||||||
|
|
||||||
from canonicaljson import encode_canonical_json
|
from canonicaljson import encode_canonical_json
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
from synapse.api.errors import (
|
from synapse.api.errors import (
|
||||||
SynapseError, CodeMessageException, FederationDeniedError,
|
SynapseError, CodeMessageException, FederationDeniedError,
|
||||||
)
|
)
|
||||||
from synapse.types import get_domain_from_id, UserID
|
from synapse.types import get_domain_from_id, UserID
|
||||||
from synapse.util.logcontext import preserve_fn, make_deferred_yieldable
|
from synapse.util.logcontext import make_deferred_yieldable, run_in_background
|
||||||
from synapse.util.retryutils import NotRetryingDestination
|
from synapse.util.retryutils import NotRetryingDestination
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -92,7 +93,7 @@ class E2eKeysHandler(object):
|
|||||||
remote_queries_not_in_cache = {}
|
remote_queries_not_in_cache = {}
|
||||||
if remote_queries:
|
if remote_queries:
|
||||||
query_list = []
|
query_list = []
|
||||||
for user_id, device_ids in remote_queries.iteritems():
|
for user_id, device_ids in iteritems(remote_queries):
|
||||||
if device_ids:
|
if device_ids:
|
||||||
query_list.extend((user_id, device_id) for device_id in device_ids)
|
query_list.extend((user_id, device_id) for device_id in device_ids)
|
||||||
else:
|
else:
|
||||||
@@ -103,9 +104,9 @@ class E2eKeysHandler(object):
|
|||||||
query_list
|
query_list
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
for user_id, devices in remote_results.iteritems():
|
for user_id, devices in iteritems(remote_results):
|
||||||
user_devices = results.setdefault(user_id, {})
|
user_devices = results.setdefault(user_id, {})
|
||||||
for device_id, device in devices.iteritems():
|
for device_id, device in iteritems(devices):
|
||||||
keys = device.get("keys", None)
|
keys = device.get("keys", None)
|
||||||
device_display_name = device.get("device_display_name", None)
|
device_display_name = device.get("device_display_name", None)
|
||||||
if keys:
|
if keys:
|
||||||
@@ -139,9 +140,9 @@ class E2eKeysHandler(object):
|
|||||||
failures[destination] = _exception_to_failure(e)
|
failures[destination] = _exception_to_failure(e)
|
||||||
|
|
||||||
yield make_deferred_yieldable(defer.gatherResults([
|
yield make_deferred_yieldable(defer.gatherResults([
|
||||||
preserve_fn(do_remote_query)(destination)
|
run_in_background(do_remote_query, destination)
|
||||||
for destination in remote_queries_not_in_cache
|
for destination in remote_queries_not_in_cache
|
||||||
]))
|
], consumeErrors=True))
|
||||||
|
|
||||||
defer.returnValue({
|
defer.returnValue({
|
||||||
"device_keys": results, "failures": failures,
|
"device_keys": results, "failures": failures,
|
||||||
@@ -242,17 +243,17 @@ class E2eKeysHandler(object):
|
|||||||
failures[destination] = _exception_to_failure(e)
|
failures[destination] = _exception_to_failure(e)
|
||||||
|
|
||||||
yield make_deferred_yieldable(defer.gatherResults([
|
yield make_deferred_yieldable(defer.gatherResults([
|
||||||
preserve_fn(claim_client_keys)(destination)
|
run_in_background(claim_client_keys, destination)
|
||||||
for destination in remote_queries
|
for destination in remote_queries
|
||||||
]))
|
], consumeErrors=True))
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"Claimed one-time-keys: %s",
|
"Claimed one-time-keys: %s",
|
||||||
",".join((
|
",".join((
|
||||||
"%s for %s:%s" % (key_id, user_id, device_id)
|
"%s for %s:%s" % (key_id, user_id, device_id)
|
||||||
for user_id, user_keys in json_result.iteritems()
|
for user_id, user_keys in iteritems(json_result)
|
||||||
for device_id, device_keys in user_keys.iteritems()
|
for device_id, device_keys in iteritems(user_keys)
|
||||||
for key_id, _ in device_keys.iteritems()
|
for key_id, _ in iteritems(device_keys)
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ class EventStreamHandler(BaseHandler):
|
|||||||
|
|
||||||
self.notifier = hs.get_notifier()
|
self.notifier = hs.get_notifier()
|
||||||
self.state = hs.get_state_handler()
|
self.state = hs.get_state_handler()
|
||||||
|
self._server_notices_sender = hs.get_server_notices_sender()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
@log_function
|
@log_function
|
||||||
@@ -58,6 +59,10 @@ class EventStreamHandler(BaseHandler):
|
|||||||
|
|
||||||
If `only_keys` is not None, events from keys will be sent down.
|
If `only_keys` is not None, events from keys will be sent down.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# send any outstanding server notices to the user.
|
||||||
|
yield self._server_notices_sender.on_user_syncing(auth_user_id)
|
||||||
|
|
||||||
auth_user = UserID.from_string(auth_user_id)
|
auth_user = UserID.from_string(auth_user_id)
|
||||||
presence_handler = self.hs.get_presence_handler()
|
presence_handler = self.hs.get_presence_handler()
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,17 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
"""Contains handlers for federation events."""
|
"""Contains handlers for federation events."""
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
from signedjson.key import decode_verify_key_bytes
|
from signedjson.key import decode_verify_key_bytes
|
||||||
from signedjson.sign import verify_signed_json
|
from signedjson.sign import verify_signed_json
|
||||||
|
import six
|
||||||
|
from six.moves import http_client
|
||||||
|
from six import iteritems
|
||||||
|
from twisted.internet import defer
|
||||||
from unpaddedbase64 import decode_base64
|
from unpaddedbase64 import decode_base64
|
||||||
|
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
@@ -43,10 +52,6 @@ from synapse.util.retryutils import NotRetryingDestination
|
|||||||
|
|
||||||
from synapse.util.distributor import user_joined_room
|
from synapse.util.distributor import user_joined_room
|
||||||
|
|
||||||
from twisted.internet import defer
|
|
||||||
|
|
||||||
import itertools
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -77,6 +82,7 @@ class FederationHandler(BaseHandler):
|
|||||||
self.pusher_pool = hs.get_pusherpool()
|
self.pusher_pool = hs.get_pusherpool()
|
||||||
self.spam_checker = hs.get_spam_checker()
|
self.spam_checker = hs.get_spam_checker()
|
||||||
self.event_creation_handler = hs.get_event_creation_handler()
|
self.event_creation_handler = hs.get_event_creation_handler()
|
||||||
|
self._server_notices_mxid = hs.config.server_notices_mxid
|
||||||
|
|
||||||
# When joining a room we need to queue any events for that room up
|
# When joining a room we need to queue any events for that room up
|
||||||
self.room_queues = {}
|
self.room_queues = {}
|
||||||
@@ -115,6 +121,19 @@ class FederationHandler(BaseHandler):
|
|||||||
logger.debug("Already seen pdu %s", pdu.event_id)
|
logger.debug("Already seen pdu %s", pdu.event_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# do some initial sanity-checking of the event. In particular, make
|
||||||
|
# sure it doesn't have hundreds of prev_events or auth_events, which
|
||||||
|
# could cause a huge state resolution or cascade of event fetches.
|
||||||
|
try:
|
||||||
|
self._sanity_check_event(pdu)
|
||||||
|
except SynapseError as err:
|
||||||
|
raise FederationError(
|
||||||
|
"ERROR",
|
||||||
|
err.code,
|
||||||
|
err.msg,
|
||||||
|
affected=pdu.event_id,
|
||||||
|
)
|
||||||
|
|
||||||
# If we are currently in the process of joining this room, then we
|
# If we are currently in the process of joining this room, then we
|
||||||
# queue up events for later processing.
|
# queue up events for later processing.
|
||||||
if pdu.room_id in self.room_queues:
|
if pdu.room_id in self.room_queues:
|
||||||
@@ -149,10 +168,6 @@ class FederationHandler(BaseHandler):
|
|||||||
|
|
||||||
auth_chain = []
|
auth_chain = []
|
||||||
|
|
||||||
have_seen = yield self.store.have_events(
|
|
||||||
[ev for ev, _ in pdu.prev_events]
|
|
||||||
)
|
|
||||||
|
|
||||||
fetch_state = False
|
fetch_state = False
|
||||||
|
|
||||||
# Get missing pdus if necessary.
|
# Get missing pdus if necessary.
|
||||||
@@ -168,7 +183,7 @@ class FederationHandler(BaseHandler):
|
|||||||
)
|
)
|
||||||
|
|
||||||
prevs = {e_id for e_id, _ in pdu.prev_events}
|
prevs = {e_id for e_id, _ in pdu.prev_events}
|
||||||
seen = set(have_seen.keys())
|
seen = yield self.store.have_seen_events(prevs)
|
||||||
|
|
||||||
if min_depth and pdu.depth < min_depth:
|
if min_depth and pdu.depth < min_depth:
|
||||||
# This is so that we don't notify the user about this
|
# This is so that we don't notify the user about this
|
||||||
@@ -196,8 +211,7 @@ class FederationHandler(BaseHandler):
|
|||||||
|
|
||||||
# Update the set of things we've seen after trying to
|
# Update the set of things we've seen after trying to
|
||||||
# fetch the missing stuff
|
# fetch the missing stuff
|
||||||
have_seen = yield self.store.have_events(prevs)
|
seen = yield self.store.have_seen_events(prevs)
|
||||||
seen = set(have_seen.iterkeys())
|
|
||||||
|
|
||||||
if not prevs - seen:
|
if not prevs - seen:
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -248,8 +262,7 @@ class FederationHandler(BaseHandler):
|
|||||||
min_depth (int): Minimum depth of events to return.
|
min_depth (int): Minimum depth of events to return.
|
||||||
"""
|
"""
|
||||||
# We recalculate seen, since it may have changed.
|
# We recalculate seen, since it may have changed.
|
||||||
have_seen = yield self.store.have_events(prevs)
|
seen = yield self.store.have_seen_events(prevs)
|
||||||
seen = set(have_seen.keys())
|
|
||||||
|
|
||||||
if not prevs - seen:
|
if not prevs - seen:
|
||||||
return
|
return
|
||||||
@@ -361,9 +374,7 @@ class FederationHandler(BaseHandler):
|
|||||||
if auth_chain:
|
if auth_chain:
|
||||||
event_ids |= {e.event_id for e in auth_chain}
|
event_ids |= {e.event_id for e in auth_chain}
|
||||||
|
|
||||||
seen_ids = set(
|
seen_ids = yield self.store.have_seen_events(event_ids)
|
||||||
(yield self.store.have_events(event_ids)).keys()
|
|
||||||
)
|
|
||||||
|
|
||||||
if state and auth_chain is not None:
|
if state and auth_chain is not None:
|
||||||
# If we have any state or auth_chain given to us by the replication
|
# If we have any state or auth_chain given to us by the replication
|
||||||
@@ -469,18 +480,18 @@ class FederationHandler(BaseHandler):
|
|||||||
# to get all state ids that we're interested in.
|
# to get all state ids that we're interested in.
|
||||||
event_map = yield self.store.get_events([
|
event_map = yield self.store.get_events([
|
||||||
e_id
|
e_id
|
||||||
for key_to_eid in event_to_state_ids.values()
|
for key_to_eid in event_to_state_ids.itervalues()
|
||||||
for key, e_id in key_to_eid.items()
|
for key, e_id in key_to_eid.iteritems()
|
||||||
if key[0] != EventTypes.Member or check_match(key[1])
|
if key[0] != EventTypes.Member or check_match(key[1])
|
||||||
])
|
])
|
||||||
|
|
||||||
event_to_state = {
|
event_to_state = {
|
||||||
e_id: {
|
e_id: {
|
||||||
key: event_map[inner_e_id]
|
key: event_map[inner_e_id]
|
||||||
for key, inner_e_id in key_to_eid.items()
|
for key, inner_e_id in key_to_eid.iteritems()
|
||||||
if inner_e_id in event_map
|
if inner_e_id in event_map
|
||||||
}
|
}
|
||||||
for e_id, key_to_eid in event_to_state_ids.items()
|
for e_id, key_to_eid in event_to_state_ids.iteritems()
|
||||||
}
|
}
|
||||||
|
|
||||||
def redact_disallowed(event, state):
|
def redact_disallowed(event, state):
|
||||||
@@ -495,7 +506,7 @@ class FederationHandler(BaseHandler):
|
|||||||
# membership states for the requesting server to determine
|
# membership states for the requesting server to determine
|
||||||
# if the server is either in the room or has been invited
|
# if the server is either in the room or has been invited
|
||||||
# into the room.
|
# into the room.
|
||||||
for ev in state.values():
|
for ev in state.itervalues():
|
||||||
if ev.type != EventTypes.Member:
|
if ev.type != EventTypes.Member:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
@@ -527,9 +538,16 @@ class FederationHandler(BaseHandler):
|
|||||||
def backfill(self, dest, room_id, limit, extremities):
|
def backfill(self, dest, room_id, limit, extremities):
|
||||||
""" Trigger a backfill request to `dest` for the given `room_id`
|
""" Trigger a backfill request to `dest` for the given `room_id`
|
||||||
|
|
||||||
This will attempt to get more events from the remote. This may return
|
This will attempt to get more events from the remote. If the other side
|
||||||
be successfull and still return no events if the other side has no new
|
has no new events to offer, this will return an empty list.
|
||||||
events to offer.
|
|
||||||
|
As the events are received, we check their signatures, and also do some
|
||||||
|
sanity-checking on them. If any of the backfilled events are invalid,
|
||||||
|
this method throws a SynapseError.
|
||||||
|
|
||||||
|
TODO: make this more useful to distinguish failures of the remote
|
||||||
|
server from invalid events (there is probably no point in trying to
|
||||||
|
re-fetch invalid events from every other HS in the room.)
|
||||||
"""
|
"""
|
||||||
if dest == self.server_name:
|
if dest == self.server_name:
|
||||||
raise SynapseError(400, "Can't backfill from self.")
|
raise SynapseError(400, "Can't backfill from self.")
|
||||||
@@ -541,6 +559,16 @@ class FederationHandler(BaseHandler):
|
|||||||
extremities=extremities,
|
extremities=extremities,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# ideally we'd sanity check the events here for excess prev_events etc,
|
||||||
|
# but it's hard to reject events at this point without completely
|
||||||
|
# breaking backfill in the same way that it is currently broken by
|
||||||
|
# events whose signature we cannot verify (#3121).
|
||||||
|
#
|
||||||
|
# So for now we accept the events anyway. #3124 tracks this.
|
||||||
|
#
|
||||||
|
# for ev in events:
|
||||||
|
# self._sanity_check_event(ev)
|
||||||
|
|
||||||
# Don't bother processing events we already have.
|
# Don't bother processing events we already have.
|
||||||
seen_events = yield self.store.have_events_in_timeline(
|
seen_events = yield self.store.have_events_in_timeline(
|
||||||
set(e.event_id for e in events)
|
set(e.event_id for e in events)
|
||||||
@@ -613,7 +641,8 @@ class FederationHandler(BaseHandler):
|
|||||||
|
|
||||||
results = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
results = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
||||||
[
|
[
|
||||||
logcontext.preserve_fn(self.replication_layer.get_pdu)(
|
logcontext.run_in_background(
|
||||||
|
self.replication_layer.get_pdu,
|
||||||
[dest],
|
[dest],
|
||||||
event_id,
|
event_id,
|
||||||
outlier=True,
|
outlier=True,
|
||||||
@@ -633,7 +662,7 @@ class FederationHandler(BaseHandler):
|
|||||||
|
|
||||||
failed_to_fetch = missing_auth - set(auth_events)
|
failed_to_fetch = missing_auth - set(auth_events)
|
||||||
|
|
||||||
seen_events = yield self.store.have_events(
|
seen_events = yield self.store.have_seen_events(
|
||||||
set(auth_events.keys()) | set(state_events.keys())
|
set(auth_events.keys()) | set(state_events.keys())
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -685,37 +714,15 @@ class FederationHandler(BaseHandler):
|
|||||||
defer.returnValue(events)
|
defer.returnValue(events)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def maybe_backfill(self, room_id, current_depth):
|
def maybe_backfill(self, room_id, extremities):
|
||||||
"""Checks the database to see if we should backfill before paginating,
|
"""Checks the database to see if we should backfill before paginating,
|
||||||
and if so do.
|
and if so do.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id (str)
|
||||||
|
extremities (list[str]): List of event_ids to backfill from. These
|
||||||
|
should be event IDs that we don't yet have.
|
||||||
"""
|
"""
|
||||||
extremities = yield self.store.get_oldest_events_with_depth_in_room(
|
|
||||||
room_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if not extremities:
|
|
||||||
logger.debug("Not backfilling as no extremeties found.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Check if we reached a point where we should start backfilling.
|
|
||||||
sorted_extremeties_tuple = sorted(
|
|
||||||
extremities.items(),
|
|
||||||
key=lambda e: -int(e[1])
|
|
||||||
)
|
|
||||||
max_depth = sorted_extremeties_tuple[0][1]
|
|
||||||
|
|
||||||
# We don't want to specify too many extremities as it causes the backfill
|
|
||||||
# request URI to be too long.
|
|
||||||
extremities = dict(sorted_extremeties_tuple[:5])
|
|
||||||
|
|
||||||
if current_depth > max_depth:
|
|
||||||
logger.debug(
|
|
||||||
"Not backfilling as we don't need to. %d < %d",
|
|
||||||
max_depth, current_depth,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Now we need to decide which hosts to hit first.
|
|
||||||
|
|
||||||
# First we try hosts that are already in the room
|
# First we try hosts that are already in the room
|
||||||
# TODO: HEURISTIC ALERT.
|
# TODO: HEURISTIC ALERT.
|
||||||
@@ -723,9 +730,19 @@ class FederationHandler(BaseHandler):
|
|||||||
curr_state = yield self.state_handler.get_current_state(room_id)
|
curr_state = yield self.state_handler.get_current_state(room_id)
|
||||||
|
|
||||||
def get_domains_from_state(state):
|
def get_domains_from_state(state):
|
||||||
|
"""Get joined domains from state
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state (dict[tuple, FrozenEvent]): State map from type/state
|
||||||
|
key to event.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[tuple[str, int]]: Returns a list of servers with the
|
||||||
|
lowest depth of their joins. Sorted by lowest depth first.
|
||||||
|
"""
|
||||||
joined_users = [
|
joined_users = [
|
||||||
(state_key, int(event.depth))
|
(state_key, int(event.depth))
|
||||||
for (e_type, state_key), event in state.items()
|
for (e_type, state_key), event in state.iteritems()
|
||||||
if e_type == EventTypes.Member
|
if e_type == EventTypes.Member
|
||||||
and event.membership == Membership.JOIN
|
and event.membership == Membership.JOIN
|
||||||
]
|
]
|
||||||
@@ -742,7 +759,7 @@ class FederationHandler(BaseHandler):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return sorted(joined_domains.items(), key=lambda d: d[1])
|
return sorted(joined_domains.iteritems(), key=lambda d: d[1])
|
||||||
|
|
||||||
curr_domains = get_domains_from_state(curr_state)
|
curr_domains = get_domains_from_state(curr_state)
|
||||||
|
|
||||||
@@ -759,7 +776,7 @@ class FederationHandler(BaseHandler):
|
|||||||
yield self.backfill(
|
yield self.backfill(
|
||||||
dom, room_id,
|
dom, room_id,
|
||||||
limit=100,
|
limit=100,
|
||||||
extremities=[e for e in extremities.keys()]
|
extremities=extremities,
|
||||||
)
|
)
|
||||||
# If this succeeded then we probably already have the
|
# If this succeeded then we probably already have the
|
||||||
# appropriate stuff.
|
# appropriate stuff.
|
||||||
@@ -805,7 +822,7 @@ class FederationHandler(BaseHandler):
|
|||||||
tried_domains = set(likely_domains)
|
tried_domains = set(likely_domains)
|
||||||
tried_domains.add(self.server_name)
|
tried_domains.add(self.server_name)
|
||||||
|
|
||||||
event_ids = list(extremities.keys())
|
event_ids = list(extremities)
|
||||||
|
|
||||||
logger.debug("calling resolve_state_groups in _maybe_backfill")
|
logger.debug("calling resolve_state_groups in _maybe_backfill")
|
||||||
resolve = logcontext.preserve_fn(
|
resolve = logcontext.preserve_fn(
|
||||||
@@ -815,34 +832,69 @@ class FederationHandler(BaseHandler):
|
|||||||
[resolve(room_id, [e]) for e in event_ids],
|
[resolve(room_id, [e]) for e in event_ids],
|
||||||
consumeErrors=True,
|
consumeErrors=True,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# dict[str, dict[tuple, str]], a map from event_id to state map of
|
||||||
|
# event_ids.
|
||||||
states = dict(zip(event_ids, [s.state for s in states]))
|
states = dict(zip(event_ids, [s.state for s in states]))
|
||||||
|
|
||||||
state_map = yield self.store.get_events(
|
state_map = yield self.store.get_events(
|
||||||
[e_id for ids in states.values() for e_id in ids],
|
[e_id for ids in states.itervalues() for e_id in ids.itervalues()],
|
||||||
get_prev_content=False
|
get_prev_content=False
|
||||||
)
|
)
|
||||||
states = {
|
states = {
|
||||||
key: {
|
key: {
|
||||||
k: state_map[e_id]
|
k: state_map[e_id]
|
||||||
for k, e_id in state_dict.items()
|
for k, e_id in state_dict.iteritems()
|
||||||
if e_id in state_map
|
if e_id in state_map
|
||||||
} for key, state_dict in states.items()
|
} for key, state_dict in states.iteritems()
|
||||||
}
|
}
|
||||||
|
|
||||||
for e_id, _ in sorted_extremeties_tuple:
|
for e_id in event_ids:
|
||||||
likely_domains = get_domains_from_state(states[e_id])
|
likely_domains = get_domains_from_state(states[e_id])
|
||||||
|
|
||||||
success = yield try_backfill([
|
success = yield try_backfill([
|
||||||
dom for dom in likely_domains
|
dom for dom, _ in likely_domains
|
||||||
if dom not in tried_domains
|
if dom not in tried_domains
|
||||||
])
|
])
|
||||||
if success:
|
if success:
|
||||||
defer.returnValue(True)
|
defer.returnValue(True)
|
||||||
|
|
||||||
tried_domains.update(likely_domains)
|
tried_domains.update(dom for dom, _ in likely_domains)
|
||||||
|
|
||||||
defer.returnValue(False)
|
defer.returnValue(False)
|
||||||
|
|
||||||
|
def _sanity_check_event(self, ev):
|
||||||
|
"""
|
||||||
|
Do some early sanity checks of a received event
|
||||||
|
|
||||||
|
In particular, checks it doesn't have an excessive number of
|
||||||
|
prev_events or auth_events, which could cause a huge state resolution
|
||||||
|
or cascade of event fetches.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ev (synapse.events.EventBase): event to be checked
|
||||||
|
|
||||||
|
Returns: None
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
SynapseError if the event does not pass muster
|
||||||
|
"""
|
||||||
|
if len(ev.prev_events) > 20:
|
||||||
|
logger.warn("Rejecting event %s which has %i prev_events",
|
||||||
|
ev.event_id, len(ev.prev_events))
|
||||||
|
raise SynapseError(
|
||||||
|
http_client.BAD_REQUEST,
|
||||||
|
"Too many prev_events",
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(ev.auth_events) > 10:
|
||||||
|
logger.warn("Rejecting event %s which has %i auth_events",
|
||||||
|
ev.event_id, len(ev.auth_events))
|
||||||
|
raise SynapseError(
|
||||||
|
http_client.BAD_REQUEST,
|
||||||
|
"Too many auth_events",
|
||||||
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def send_invite(self, target_host, event):
|
def send_invite(self, target_host, event):
|
||||||
""" Sends the invite to the remote server for signing.
|
""" Sends the invite to the remote server for signing.
|
||||||
@@ -967,7 +1019,7 @@ class FederationHandler(BaseHandler):
|
|||||||
# lots of requests for missing prev_events which we do actually
|
# lots of requests for missing prev_events which we do actually
|
||||||
# have. Hence we fire off the deferred, but don't wait for it.
|
# have. Hence we fire off the deferred, but don't wait for it.
|
||||||
|
|
||||||
logcontext.preserve_fn(self._handle_queued_pdus)(room_queue)
|
logcontext.run_in_background(self._handle_queued_pdus, room_queue)
|
||||||
|
|
||||||
defer.returnValue(True)
|
defer.returnValue(True)
|
||||||
|
|
||||||
@@ -1121,6 +1173,13 @@ class FederationHandler(BaseHandler):
|
|||||||
if not self.is_mine_id(event.state_key):
|
if not self.is_mine_id(event.state_key):
|
||||||
raise SynapseError(400, "The invite event must be for this server")
|
raise SynapseError(400, "The invite event must be for this server")
|
||||||
|
|
||||||
|
# block any attempts to invite the server notices mxid
|
||||||
|
if event.state_key == self._server_notices_mxid:
|
||||||
|
raise SynapseError(
|
||||||
|
http_client.FORBIDDEN,
|
||||||
|
"Cannot invite this user",
|
||||||
|
)
|
||||||
|
|
||||||
event.internal_metadata.outlier = True
|
event.internal_metadata.outlier = True
|
||||||
event.internal_metadata.invite_from_remote = True
|
event.internal_metadata.invite_from_remote = True
|
||||||
|
|
||||||
@@ -1308,7 +1367,7 @@ class FederationHandler(BaseHandler):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if state_groups:
|
if state_groups:
|
||||||
_, state = state_groups.items().pop()
|
_, state = list(iteritems(state_groups)).pop()
|
||||||
results = {
|
results = {
|
||||||
(e.type, e.state_key): e for e in state
|
(e.type, e.state_key): e for e in state
|
||||||
}
|
}
|
||||||
@@ -1457,18 +1516,21 @@ class FederationHandler(BaseHandler):
|
|||||||
backfilled=backfilled,
|
backfilled=backfilled,
|
||||||
)
|
)
|
||||||
except: # noqa: E722, as we reraise the exception this is fine.
|
except: # noqa: E722, as we reraise the exception this is fine.
|
||||||
# Ensure that we actually remove the entries in the push actions
|
tp, value, tb = sys.exc_info()
|
||||||
# staging area
|
|
||||||
logcontext.preserve_fn(
|
logcontext.run_in_background(
|
||||||
self.store.remove_push_actions_from_staging
|
self.store.remove_push_actions_from_staging,
|
||||||
)(event.event_id)
|
event.event_id,
|
||||||
raise
|
)
|
||||||
|
|
||||||
|
six.reraise(tp, value, tb)
|
||||||
|
|
||||||
if not backfilled:
|
if not backfilled:
|
||||||
# this intentionally does not yield: we don't care about the result
|
# this intentionally does not yield: we don't care about the result
|
||||||
# and don't need to wait for it.
|
# and don't need to wait for it.
|
||||||
logcontext.preserve_fn(self.pusher_pool.on_new_notifications)(
|
logcontext.run_in_background(
|
||||||
event_stream_id, max_stream_id
|
self.pusher_pool.on_new_notifications,
|
||||||
|
event_stream_id, max_stream_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
defer.returnValue((context, event_stream_id, max_stream_id))
|
defer.returnValue((context, event_stream_id, max_stream_id))
|
||||||
@@ -1482,7 +1544,8 @@ class FederationHandler(BaseHandler):
|
|||||||
"""
|
"""
|
||||||
contexts = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
contexts = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
||||||
[
|
[
|
||||||
logcontext.preserve_fn(self._prep_event)(
|
logcontext.run_in_background(
|
||||||
|
self._prep_event,
|
||||||
origin,
|
origin,
|
||||||
ev_info["event"],
|
ev_info["event"],
|
||||||
state=ev_info.get("state"),
|
state=ev_info.get("state"),
|
||||||
@@ -1736,7 +1799,8 @@ class FederationHandler(BaseHandler):
|
|||||||
event_key = None
|
event_key = None
|
||||||
|
|
||||||
if event_auth_events - current_state:
|
if event_auth_events - current_state:
|
||||||
have_events = yield self.store.have_events(
|
# TODO: can we use store.have_seen_events here instead?
|
||||||
|
have_events = yield self.store.get_seen_events_with_rejections(
|
||||||
event_auth_events - current_state
|
event_auth_events - current_state
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -1759,12 +1823,12 @@ class FederationHandler(BaseHandler):
|
|||||||
origin, event.room_id, event.event_id
|
origin, event.room_id, event.event_id
|
||||||
)
|
)
|
||||||
|
|
||||||
seen_remotes = yield self.store.have_events(
|
seen_remotes = yield self.store.have_seen_events(
|
||||||
[e.event_id for e in remote_auth_chain]
|
[e.event_id for e in remote_auth_chain]
|
||||||
)
|
)
|
||||||
|
|
||||||
for e in remote_auth_chain:
|
for e in remote_auth_chain:
|
||||||
if e.event_id in seen_remotes.keys():
|
if e.event_id in seen_remotes:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if e.event_id == event.event_id:
|
if e.event_id == event.event_id:
|
||||||
@@ -1791,7 +1855,7 @@ class FederationHandler(BaseHandler):
|
|||||||
except AuthError:
|
except AuthError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
have_events = yield self.store.have_events(
|
have_events = yield self.store.get_seen_events_with_rejections(
|
||||||
[e_id for e_id, _ in event.auth_events]
|
[e_id for e_id, _ in event.auth_events]
|
||||||
)
|
)
|
||||||
seen_events = set(have_events.keys())
|
seen_events = set(have_events.keys())
|
||||||
@@ -1810,7 +1874,8 @@ class FederationHandler(BaseHandler):
|
|||||||
|
|
||||||
different_events = yield logcontext.make_deferred_yieldable(
|
different_events = yield logcontext.make_deferred_yieldable(
|
||||||
defer.gatherResults([
|
defer.gatherResults([
|
||||||
logcontext.preserve_fn(self.store.get_event)(
|
logcontext.run_in_background(
|
||||||
|
self.store.get_event,
|
||||||
d,
|
d,
|
||||||
allow_none=True,
|
allow_none=True,
|
||||||
allow_rejected=False,
|
allow_rejected=False,
|
||||||
@@ -1876,13 +1941,13 @@ class FederationHandler(BaseHandler):
|
|||||||
local_auth_chain,
|
local_auth_chain,
|
||||||
)
|
)
|
||||||
|
|
||||||
seen_remotes = yield self.store.have_events(
|
seen_remotes = yield self.store.have_seen_events(
|
||||||
[e.event_id for e in result["auth_chain"]]
|
[e.event_id for e in result["auth_chain"]]
|
||||||
)
|
)
|
||||||
|
|
||||||
# 3. Process any remote auth chain events we haven't seen.
|
# 3. Process any remote auth chain events we haven't seen.
|
||||||
for ev in result["auth_chain"]:
|
for ev in result["auth_chain"]:
|
||||||
if ev.event_id in seen_remotes.keys():
|
if ev.event_id in seen_remotes:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if ev.event_id == event.event_id:
|
if ev.event_id == event.event_id:
|
||||||
@@ -1948,7 +2013,7 @@ class FederationHandler(BaseHandler):
|
|||||||
this will not be included in the current_state in the context.
|
this will not be included in the current_state in the context.
|
||||||
"""
|
"""
|
||||||
state_updates = {
|
state_updates = {
|
||||||
k: a.event_id for k, a in auth_events.iteritems()
|
k: a.event_id for k, a in iteritems(auth_events)
|
||||||
if k != event_key
|
if k != event_key
|
||||||
}
|
}
|
||||||
context.current_state_ids = dict(context.current_state_ids)
|
context.current_state_ids = dict(context.current_state_ids)
|
||||||
@@ -1958,7 +2023,7 @@ class FederationHandler(BaseHandler):
|
|||||||
context.delta_ids.update(state_updates)
|
context.delta_ids.update(state_updates)
|
||||||
context.prev_state_ids = dict(context.prev_state_ids)
|
context.prev_state_ids = dict(context.prev_state_ids)
|
||||||
context.prev_state_ids.update({
|
context.prev_state_ids.update({
|
||||||
k: a.event_id for k, a in auth_events.iteritems()
|
k: a.event_id for k, a in iteritems(auth_events)
|
||||||
})
|
})
|
||||||
context.state_group = yield self.store.store_state_group(
|
context.state_group = yield self.store.store_state_group(
|
||||||
event.event_id,
|
event.event_id,
|
||||||
@@ -2010,7 +2075,7 @@ class FederationHandler(BaseHandler):
|
|||||||
|
|
||||||
def get_next(it, opt=None):
|
def get_next(it, opt=None):
|
||||||
try:
|
try:
|
||||||
return it.next()
|
return next(it)
|
||||||
except Exception:
|
except Exception:
|
||||||
return opt
|
return opt
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
from synapse.types import get_domain_from_id
|
from synapse.types import get_domain_from_id
|
||||||
@@ -449,7 +450,7 @@ class GroupsLocalHandler(object):
|
|||||||
|
|
||||||
results = {}
|
results = {}
|
||||||
failed_results = []
|
failed_results = []
|
||||||
for destination, dest_user_ids in destinations.iteritems():
|
for destination, dest_user_ids in iteritems(destinations):
|
||||||
try:
|
try:
|
||||||
r = yield self.transport_client.bulk_get_publicised_groups(
|
r = yield self.transport_client.bulk_get_publicised_groups(
|
||||||
destination, list(dest_user_ids),
|
destination, list(dest_user_ids),
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from synapse.types import (
|
|||||||
from synapse.util import unwrapFirstError
|
from synapse.util import unwrapFirstError
|
||||||
from synapse.util.async import concurrently_execute
|
from synapse.util.async import concurrently_execute
|
||||||
from synapse.util.caches.snapshot_cache import SnapshotCache
|
from synapse.util.caches.snapshot_cache import SnapshotCache
|
||||||
from synapse.util.logcontext import make_deferred_yieldable, preserve_fn
|
from synapse.util.logcontext import make_deferred_yieldable, run_in_background
|
||||||
from synapse.visibility import filter_events_for_client
|
from synapse.visibility import filter_events_for_client
|
||||||
|
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
@@ -166,7 +166,8 @@ class InitialSyncHandler(BaseHandler):
|
|||||||
(messages, token), current_state = yield make_deferred_yieldable(
|
(messages, token), current_state = yield make_deferred_yieldable(
|
||||||
defer.gatherResults(
|
defer.gatherResults(
|
||||||
[
|
[
|
||||||
preserve_fn(self.store.get_recent_events_for_room)(
|
run_in_background(
|
||||||
|
self.store.get_recent_events_for_room,
|
||||||
event.room_id,
|
event.room_id,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
end_token=room_end_token,
|
end_token=room_end_token,
|
||||||
@@ -180,8 +181,8 @@ class InitialSyncHandler(BaseHandler):
|
|||||||
self.store, user_id, messages
|
self.store, user_id, messages
|
||||||
)
|
)
|
||||||
|
|
||||||
start_token = now_token.copy_and_replace("room_key", token[0])
|
start_token = now_token.copy_and_replace("room_key", token)
|
||||||
end_token = now_token.copy_and_replace("room_key", token[1])
|
end_token = now_token.copy_and_replace("room_key", room_end_token)
|
||||||
time_now = self.clock.time_msec()
|
time_now = self.clock.time_msec()
|
||||||
|
|
||||||
d["messages"] = {
|
d["messages"] = {
|
||||||
@@ -324,8 +325,8 @@ class InitialSyncHandler(BaseHandler):
|
|||||||
self.store, user_id, messages, is_peeking=is_peeking
|
self.store, user_id, messages, is_peeking=is_peeking
|
||||||
)
|
)
|
||||||
|
|
||||||
start_token = StreamToken.START.copy_and_replace("room_key", token[0])
|
start_token = StreamToken.START.copy_and_replace("room_key", token)
|
||||||
end_token = StreamToken.START.copy_and_replace("room_key", token[1])
|
end_token = StreamToken.START.copy_and_replace("room_key", stream_token)
|
||||||
|
|
||||||
time_now = self.clock.time_msec()
|
time_now = self.clock.time_msec()
|
||||||
|
|
||||||
@@ -391,9 +392,10 @@ class InitialSyncHandler(BaseHandler):
|
|||||||
|
|
||||||
presence, receipts, (messages, token) = yield defer.gatherResults(
|
presence, receipts, (messages, token) = yield defer.gatherResults(
|
||||||
[
|
[
|
||||||
preserve_fn(get_presence)(),
|
run_in_background(get_presence),
|
||||||
preserve_fn(get_receipts)(),
|
run_in_background(get_receipts),
|
||||||
preserve_fn(self.store.get_recent_events_for_room)(
|
run_in_background(
|
||||||
|
self.store.get_recent_events_for_room,
|
||||||
room_id,
|
room_id,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
end_token=now_token.room_key,
|
end_token=now_token.room_key,
|
||||||
@@ -406,8 +408,8 @@ class InitialSyncHandler(BaseHandler):
|
|||||||
self.store, user_id, messages, is_peeking=is_peeking,
|
self.store, user_id, messages, is_peeking=is_peeking,
|
||||||
)
|
)
|
||||||
|
|
||||||
start_token = now_token.copy_and_replace("room_key", token[0])
|
start_token = now_token.copy_and_replace("room_key", token)
|
||||||
end_token = now_token.copy_and_replace("room_key", token[1])
|
end_token = now_token
|
||||||
|
|
||||||
time_now = self.clock.time_msec()
|
time_now = self.clock.time_msec()
|
||||||
|
|
||||||
|
|||||||
@@ -13,11 +13,23 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import logging
|
||||||
|
import simplejson
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from canonicaljson import encode_canonical_json
|
||||||
|
import six
|
||||||
|
from six import string_types, itervalues, iteritems
|
||||||
from twisted.internet import defer, reactor
|
from twisted.internet import defer, reactor
|
||||||
|
from twisted.internet.defer import succeed
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
|
|
||||||
from synapse.api.constants import EventTypes, Membership
|
from synapse.api.constants import EventTypes, Membership, MAX_DEPTH
|
||||||
from synapse.api.errors import AuthError, Codes, SynapseError
|
from synapse.api.errors import (
|
||||||
|
AuthError, Codes, SynapseError,
|
||||||
|
ConsentNotGivenError,
|
||||||
|
)
|
||||||
|
from synapse.api.urls import ConsentURIBuilder
|
||||||
from synapse.crypto.event_signing import add_hashes_and_signatures
|
from synapse.crypto.event_signing import add_hashes_and_signatures
|
||||||
from synapse.events.utils import serialize_event
|
from synapse.events.utils import serialize_event
|
||||||
from synapse.events.validator import EventValidator
|
from synapse.events.validator import EventValidator
|
||||||
@@ -25,7 +37,7 @@ from synapse.types import (
|
|||||||
UserID, RoomAlias, RoomStreamToken,
|
UserID, RoomAlias, RoomStreamToken,
|
||||||
)
|
)
|
||||||
from synapse.util.async import run_on_reactor, ReadWriteLock, Limiter
|
from synapse.util.async import run_on_reactor, ReadWriteLock, Limiter
|
||||||
from synapse.util.logcontext import preserve_fn, run_in_background
|
from synapse.util.logcontext import run_in_background
|
||||||
from synapse.util.metrics import measure_func
|
from synapse.util.metrics import measure_func
|
||||||
from synapse.util.frozenutils import frozendict_json_encoder
|
from synapse.util.frozenutils import frozendict_json_encoder
|
||||||
from synapse.util.stringutils import random_string
|
from synapse.util.stringutils import random_string
|
||||||
@@ -34,12 +46,6 @@ from synapse.replication.http.send_event import send_event_to_master
|
|||||||
|
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
|
|
||||||
from canonicaljson import encode_canonical_json
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import random
|
|
||||||
import simplejson
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -86,14 +92,14 @@ class MessageHandler(BaseHandler):
|
|||||||
# map from purge id to PurgeStatus
|
# map from purge id to PurgeStatus
|
||||||
self._purges_by_id = {}
|
self._purges_by_id = {}
|
||||||
|
|
||||||
def start_purge_history(self, room_id, topological_ordering,
|
def start_purge_history(self, room_id, token,
|
||||||
delete_local_events=False):
|
delete_local_events=False):
|
||||||
"""Start off a history purge on a room.
|
"""Start off a history purge on a room.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
room_id (str): The room to purge from
|
room_id (str): The room to purge from
|
||||||
|
|
||||||
topological_ordering (int): minimum topo ordering to preserve
|
token (str): topological token to delete events before
|
||||||
delete_local_events (bool): True to delete local events as well as
|
delete_local_events (bool): True to delete local events as well as
|
||||||
remote ones
|
remote ones
|
||||||
|
|
||||||
@@ -115,19 +121,19 @@ class MessageHandler(BaseHandler):
|
|||||||
self._purges_by_id[purge_id] = PurgeStatus()
|
self._purges_by_id[purge_id] = PurgeStatus()
|
||||||
run_in_background(
|
run_in_background(
|
||||||
self._purge_history,
|
self._purge_history,
|
||||||
purge_id, room_id, topological_ordering, delete_local_events,
|
purge_id, room_id, token, delete_local_events,
|
||||||
)
|
)
|
||||||
return purge_id
|
return purge_id
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _purge_history(self, purge_id, room_id, topological_ordering,
|
def _purge_history(self, purge_id, room_id, token,
|
||||||
delete_local_events):
|
delete_local_events):
|
||||||
"""Carry out a history purge on a room.
|
"""Carry out a history purge on a room.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
purge_id (str): The id for this purge
|
purge_id (str): The id for this purge
|
||||||
room_id (str): The room to purge from
|
room_id (str): The room to purge from
|
||||||
topological_ordering (int): minimum topo ordering to preserve
|
token (str): topological token to delete events before
|
||||||
delete_local_events (bool): True to delete local events as well as
|
delete_local_events (bool): True to delete local events as well as
|
||||||
remote ones
|
remote ones
|
||||||
|
|
||||||
@@ -138,7 +144,7 @@ class MessageHandler(BaseHandler):
|
|||||||
try:
|
try:
|
||||||
with (yield self.pagination_lock.write(room_id)):
|
with (yield self.pagination_lock.write(room_id)):
|
||||||
yield self.store.purge_history(
|
yield self.store.purge_history(
|
||||||
room_id, topological_ordering, delete_local_events,
|
room_id, token, delete_local_events,
|
||||||
)
|
)
|
||||||
logger.info("[purge] complete")
|
logger.info("[purge] complete")
|
||||||
self._purges_by_id[purge_id].status = PurgeStatus.STATUS_COMPLETE
|
self._purges_by_id[purge_id].status = PurgeStatus.STATUS_COMPLETE
|
||||||
@@ -205,31 +211,19 @@ class MessageHandler(BaseHandler):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if source_config.direction == 'b':
|
if source_config.direction == 'b':
|
||||||
# if we're going backwards, we might need to backfill. This
|
|
||||||
# requires that we have a topo token.
|
|
||||||
if room_token.topological:
|
|
||||||
max_topo = room_token.topological
|
|
||||||
else:
|
|
||||||
max_topo = yield self.store.get_max_topological_token(
|
|
||||||
room_id, room_token.stream
|
|
||||||
)
|
|
||||||
|
|
||||||
if membership == Membership.LEAVE:
|
if membership == Membership.LEAVE:
|
||||||
# If they have left the room then clamp the token to be before
|
# If they have left the room then clamp the token to be before
|
||||||
# they left the room, to save the effort of loading from the
|
# they left the room, to save the effort of loading from the
|
||||||
# database.
|
# database.
|
||||||
|
|
||||||
leave_token = yield self.store.get_topological_token_for_event(
|
leave_token = yield self.store.get_topological_token_for_event(
|
||||||
member_event_id
|
member_event_id,
|
||||||
|
)
|
||||||
|
source_config.from_key = yield self.store.clamp_token_before(
|
||||||
|
room_id, source_config.from_key, leave_token,
|
||||||
)
|
)
|
||||||
leave_token = RoomStreamToken.parse(leave_token)
|
|
||||||
if leave_token.topological < max_topo:
|
|
||||||
source_config.from_key = str(leave_token)
|
|
||||||
|
|
||||||
yield self.hs.get_handlers().federation_handler.maybe_backfill(
|
events, next_key, extremities = yield self.store.paginate_room_events(
|
||||||
room_id, max_topo
|
|
||||||
)
|
|
||||||
|
|
||||||
events, next_key = yield self.store.paginate_room_events(
|
|
||||||
room_id=room_id,
|
room_id=room_id,
|
||||||
from_key=source_config.from_key,
|
from_key=source_config.from_key,
|
||||||
to_key=source_config.to_key,
|
to_key=source_config.to_key,
|
||||||
@@ -238,6 +232,20 @@ class MessageHandler(BaseHandler):
|
|||||||
event_filter=event_filter,
|
event_filter=event_filter,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if source_config.direction == 'b' and extremities:
|
||||||
|
yield self.hs.get_handlers().federation_handler.maybe_backfill(
|
||||||
|
room_id, extremities
|
||||||
|
)
|
||||||
|
|
||||||
|
events, next_key, extremities = yield self.store.paginate_room_events(
|
||||||
|
room_id=room_id,
|
||||||
|
from_key=source_config.from_key,
|
||||||
|
to_key=source_config.to_key,
|
||||||
|
direction=source_config.direction,
|
||||||
|
limit=source_config.limit,
|
||||||
|
event_filter=event_filter,
|
||||||
|
)
|
||||||
|
|
||||||
next_token = pagin_config.from_token.copy_and_replace(
|
next_token = pagin_config.from_token.copy_and_replace(
|
||||||
"room_key", next_key
|
"room_key", next_key
|
||||||
)
|
)
|
||||||
@@ -397,7 +405,7 @@ class MessageHandler(BaseHandler):
|
|||||||
"avatar_url": profile.avatar_url,
|
"avatar_url": profile.avatar_url,
|
||||||
"display_name": profile.display_name,
|
"display_name": profile.display_name,
|
||||||
}
|
}
|
||||||
for user_id, profile in users_with_profile.iteritems()
|
for user_id, profile in iteritems(users_with_profile)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -431,9 +439,12 @@ class EventCreationHandler(object):
|
|||||||
|
|
||||||
self.spam_checker = hs.get_spam_checker()
|
self.spam_checker = hs.get_spam_checker()
|
||||||
|
|
||||||
|
if self.config.block_events_without_consent_error is not None:
|
||||||
|
self._consent_uri_builder = ConsentURIBuilder(self.config)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def create_event(self, requester, event_dict, token_id=None, txn_id=None,
|
def create_event(self, requester, event_dict, token_id=None, txn_id=None,
|
||||||
prev_event_ids=None):
|
prev_events_and_hashes=None):
|
||||||
"""
|
"""
|
||||||
Given a dict from a client, create a new event.
|
Given a dict from a client, create a new event.
|
||||||
|
|
||||||
@@ -447,7 +458,13 @@ class EventCreationHandler(object):
|
|||||||
event_dict (dict): An entire event
|
event_dict (dict): An entire event
|
||||||
token_id (str)
|
token_id (str)
|
||||||
txn_id (str)
|
txn_id (str)
|
||||||
prev_event_ids (list): The prev event ids to use when creating the event
|
|
||||||
|
prev_events_and_hashes (list[(str, dict[str, str], int)]|None):
|
||||||
|
the forward extremities to use as the prev_events for the
|
||||||
|
new event. For each event, a tuple of (event_id, hashes, depth)
|
||||||
|
where *hashes* is a map from algorithm to hash.
|
||||||
|
|
||||||
|
If None, they will be requested from the database.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple of created event (FrozenEvent), Context
|
Tuple of created event (FrozenEvent), Context
|
||||||
@@ -476,6 +493,10 @@ class EventCreationHandler(object):
|
|||||||
target, e
|
target, e
|
||||||
)
|
)
|
||||||
|
|
||||||
|
is_exempt = yield self._is_exempt_from_privacy_policy(builder)
|
||||||
|
if not is_exempt:
|
||||||
|
yield self.assert_accepted_privacy_policy(requester)
|
||||||
|
|
||||||
if token_id is not None:
|
if token_id is not None:
|
||||||
builder.internal_metadata.token_id = token_id
|
builder.internal_metadata.token_id = token_id
|
||||||
|
|
||||||
@@ -485,11 +506,88 @@ class EventCreationHandler(object):
|
|||||||
event, context = yield self.create_new_client_event(
|
event, context = yield self.create_new_client_event(
|
||||||
builder=builder,
|
builder=builder,
|
||||||
requester=requester,
|
requester=requester,
|
||||||
prev_event_ids=prev_event_ids,
|
prev_events_and_hashes=prev_events_and_hashes,
|
||||||
)
|
)
|
||||||
|
|
||||||
defer.returnValue((event, context))
|
defer.returnValue((event, context))
|
||||||
|
|
||||||
|
def _is_exempt_from_privacy_policy(self, builder):
|
||||||
|
""""Determine if an event to be sent is exempt from having to consent
|
||||||
|
to the privacy policy
|
||||||
|
|
||||||
|
Args:
|
||||||
|
builder (synapse.events.builder.EventBuilder): event being created
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Deferred[bool]: true if the event can be sent without the user
|
||||||
|
consenting
|
||||||
|
"""
|
||||||
|
# the only thing the user can do is join the server notices room.
|
||||||
|
if builder.type == EventTypes.Member:
|
||||||
|
membership = builder.content.get("membership", None)
|
||||||
|
if membership == Membership.JOIN:
|
||||||
|
return self._is_server_notices_room(builder.room_id)
|
||||||
|
return succeed(False)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _is_server_notices_room(self, room_id):
|
||||||
|
if self.config.server_notices_mxid is None:
|
||||||
|
defer.returnValue(False)
|
||||||
|
user_ids = yield self.store.get_users_in_room(room_id)
|
||||||
|
defer.returnValue(self.config.server_notices_mxid in user_ids)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def assert_accepted_privacy_policy(self, requester):
|
||||||
|
"""Check if a user has accepted the privacy policy
|
||||||
|
|
||||||
|
Called when the given user is about to do something that requires
|
||||||
|
privacy consent. We see if the user is exempt and otherwise check that
|
||||||
|
they have given consent. If they have not, a ConsentNotGiven error is
|
||||||
|
raised.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
requester (synapse.types.Requester):
|
||||||
|
The user making the request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Deferred[None]: returns normally if the user has consented or is
|
||||||
|
exempt
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ConsentNotGivenError: if the user has not given consent yet
|
||||||
|
"""
|
||||||
|
if self.config.block_events_without_consent_error is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# exempt AS users from needing consent
|
||||||
|
if requester.app_service is not None:
|
||||||
|
return
|
||||||
|
|
||||||
|
user_id = requester.user.to_string()
|
||||||
|
|
||||||
|
# exempt the system notices user
|
||||||
|
if (
|
||||||
|
self.config.server_notices_mxid is not None and
|
||||||
|
user_id == self.config.server_notices_mxid
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
u = yield self.store.get_user_by_id(user_id)
|
||||||
|
assert u is not None
|
||||||
|
if u["consent_version"] == self.config.user_consent_version:
|
||||||
|
return
|
||||||
|
|
||||||
|
consent_uri = self._consent_uri_builder.build_user_consent_uri(
|
||||||
|
requester.user.localpart,
|
||||||
|
)
|
||||||
|
msg = self.config.block_events_without_consent_error % {
|
||||||
|
'consent_uri': consent_uri,
|
||||||
|
}
|
||||||
|
raise ConsentNotGivenError(
|
||||||
|
msg=msg,
|
||||||
|
consent_uri=consent_uri,
|
||||||
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def send_nonmember_event(self, requester, event, context, ratelimit=True):
|
def send_nonmember_event(self, requester, event, context, ratelimit=True):
|
||||||
"""
|
"""
|
||||||
@@ -572,7 +670,7 @@ class EventCreationHandler(object):
|
|||||||
|
|
||||||
spam_error = self.spam_checker.check_event_for_spam(event)
|
spam_error = self.spam_checker.check_event_for_spam(event)
|
||||||
if spam_error:
|
if spam_error:
|
||||||
if not isinstance(spam_error, basestring):
|
if not isinstance(spam_error, string_types):
|
||||||
spam_error = "Spam is not permitted here"
|
spam_error = "Spam is not permitted here"
|
||||||
raise SynapseError(
|
raise SynapseError(
|
||||||
403, spam_error, Codes.FORBIDDEN
|
403, spam_error, Codes.FORBIDDEN
|
||||||
@@ -588,39 +686,48 @@ class EventCreationHandler(object):
|
|||||||
|
|
||||||
@measure_func("create_new_client_event")
|
@measure_func("create_new_client_event")
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def create_new_client_event(self, builder, requester=None, prev_event_ids=None):
|
def create_new_client_event(self, builder, requester=None,
|
||||||
if prev_event_ids:
|
prev_events_and_hashes=None):
|
||||||
prev_events = yield self.store.add_event_hashes(prev_event_ids)
|
"""Create a new event for a local client
|
||||||
prev_max_depth = yield self.store.get_max_depth_of_events(prev_event_ids)
|
|
||||||
depth = prev_max_depth + 1
|
Args:
|
||||||
else:
|
builder (EventBuilder):
|
||||||
latest_ret = yield self.store.get_latest_event_ids_and_hashes_in_room(
|
|
||||||
builder.room_id,
|
requester (synapse.types.Requester|None):
|
||||||
|
|
||||||
|
prev_events_and_hashes (list[(str, dict[str, str], int)]|None):
|
||||||
|
the forward extremities to use as the prev_events for the
|
||||||
|
new event. For each event, a tuple of (event_id, hashes, depth)
|
||||||
|
where *hashes* is a map from algorithm to hash.
|
||||||
|
|
||||||
|
If None, they will be requested from the database.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Deferred[(synapse.events.EventBase, synapse.events.snapshot.EventContext)]
|
||||||
|
"""
|
||||||
|
|
||||||
|
if prev_events_and_hashes is not None:
|
||||||
|
assert len(prev_events_and_hashes) <= 10, \
|
||||||
|
"Attempting to create an event with %i prev_events" % (
|
||||||
|
len(prev_events_and_hashes),
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
prev_events_and_hashes = \
|
||||||
|
yield self.store.get_prev_events_for_room(builder.room_id)
|
||||||
|
|
||||||
# We want to limit the max number of prev events we point to in our
|
if prev_events_and_hashes:
|
||||||
# new event
|
depth = max([d for _, _, d in prev_events_and_hashes]) + 1
|
||||||
if len(latest_ret) > 10:
|
# we cap depth of generated events, to ensure that they are not
|
||||||
# Sort by reverse depth, so we point to the most recent.
|
# rejected by other servers (and so that they can be persisted in
|
||||||
latest_ret.sort(key=lambda a: -a[2])
|
# the db)
|
||||||
new_latest_ret = latest_ret[:5]
|
depth = min(depth, MAX_DEPTH)
|
||||||
|
else:
|
||||||
|
depth = 1
|
||||||
|
|
||||||
# We also randomly point to some of the older events, to make
|
prev_events = [
|
||||||
# sure that we don't completely ignore the older events.
|
(event_id, prev_hashes)
|
||||||
if latest_ret[5:]:
|
for event_id, prev_hashes, _ in prev_events_and_hashes
|
||||||
sample_size = min(5, len(latest_ret[5:]))
|
]
|
||||||
new_latest_ret.extend(random.sample(latest_ret[5:], sample_size))
|
|
||||||
latest_ret = new_latest_ret
|
|
||||||
|
|
||||||
if latest_ret:
|
|
||||||
depth = max([d for _, _, d in latest_ret]) + 1
|
|
||||||
else:
|
|
||||||
depth = 1
|
|
||||||
|
|
||||||
prev_events = [
|
|
||||||
(event_id, prev_hashes)
|
|
||||||
for event_id, prev_hashes, _ in latest_ret
|
|
||||||
]
|
|
||||||
|
|
||||||
builder.prev_events = prev_events
|
builder.prev_events = prev_events
|
||||||
builder.depth = depth
|
builder.depth = depth
|
||||||
@@ -719,8 +826,14 @@ class EventCreationHandler(object):
|
|||||||
except: # noqa: E722, as we reraise the exception this is fine.
|
except: # noqa: E722, as we reraise the exception this is fine.
|
||||||
# Ensure that we actually remove the entries in the push actions
|
# Ensure that we actually remove the entries in the push actions
|
||||||
# staging area, if we calculated them.
|
# staging area, if we calculated them.
|
||||||
preserve_fn(self.store.remove_push_actions_from_staging)(event.event_id)
|
tp, value, tb = sys.exc_info()
|
||||||
raise
|
|
||||||
|
run_in_background(
|
||||||
|
self.store.remove_push_actions_from_staging,
|
||||||
|
event.event_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
six.reraise(tp, value, tb)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def persist_and_notify_client_event(
|
def persist_and_notify_client_event(
|
||||||
@@ -771,7 +884,7 @@ class EventCreationHandler(object):
|
|||||||
|
|
||||||
state_to_include_ids = [
|
state_to_include_ids = [
|
||||||
e_id
|
e_id
|
||||||
for k, e_id in context.current_state_ids.iteritems()
|
for k, e_id in iteritems(context.current_state_ids)
|
||||||
if k[0] in self.hs.config.room_invite_state_types
|
if k[0] in self.hs.config.room_invite_state_types
|
||||||
or k == (EventTypes.Member, event.sender)
|
or k == (EventTypes.Member, event.sender)
|
||||||
]
|
]
|
||||||
@@ -785,7 +898,7 @@ class EventCreationHandler(object):
|
|||||||
"content": e.content,
|
"content": e.content,
|
||||||
"sender": e.sender,
|
"sender": e.sender,
|
||||||
}
|
}
|
||||||
for e in state_to_include.itervalues()
|
for e in itervalues(state_to_include)
|
||||||
]
|
]
|
||||||
|
|
||||||
invitee = UserID.from_string(event.state_key)
|
invitee = UserID.from_string(event.state_key)
|
||||||
@@ -840,22 +953,33 @@ class EventCreationHandler(object):
|
|||||||
|
|
||||||
# this intentionally does not yield: we don't care about the result
|
# this intentionally does not yield: we don't care about the result
|
||||||
# and don't need to wait for it.
|
# and don't need to wait for it.
|
||||||
preserve_fn(self.pusher_pool.on_new_notifications)(
|
run_in_background(
|
||||||
|
self.pusher_pool.on_new_notifications,
|
||||||
event_stream_id, max_stream_id
|
event_stream_id, max_stream_id
|
||||||
)
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _notify():
|
def _notify():
|
||||||
yield run_on_reactor()
|
yield run_on_reactor()
|
||||||
self.notifier.on_new_room_event(
|
try:
|
||||||
event, event_stream_id, max_stream_id,
|
self.notifier.on_new_room_event(
|
||||||
extra_users=extra_users
|
event, event_stream_id, max_stream_id,
|
||||||
)
|
extra_users=extra_users
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error notifying about new room event")
|
||||||
|
|
||||||
preserve_fn(_notify)()
|
run_in_background(_notify)
|
||||||
|
|
||||||
if event.type == EventTypes.Message:
|
if event.type == EventTypes.Message:
|
||||||
presence = self.hs.get_presence_handler()
|
|
||||||
# We don't want to block sending messages on any presence code. This
|
# We don't want to block sending messages on any presence code. This
|
||||||
# matters as sometimes presence code can take a while.
|
# matters as sometimes presence code can take a while.
|
||||||
preserve_fn(presence.bump_presence_active_time)(requester.user)
|
run_in_background(self._bump_active_time, requester.user)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _bump_active_time(self, user):
|
||||||
|
try:
|
||||||
|
presence = self.hs.get_presence_handler()
|
||||||
|
yield presence.bump_presence_active_time(user)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error bumping presence active time")
|
||||||
|
|||||||
@@ -25,13 +25,15 @@ The methods that define policy are:
|
|||||||
from twisted.internet import defer, reactor
|
from twisted.internet import defer, reactor
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
from six import itervalues, iteritems
|
||||||
|
|
||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
from synapse.api.constants import PresenceState
|
from synapse.api.constants import PresenceState
|
||||||
from synapse.storage.presence import UserPresenceState
|
from synapse.storage.presence import UserPresenceState
|
||||||
|
|
||||||
from synapse.util.caches.descriptors import cachedInlineCallbacks
|
from synapse.util.caches.descriptors import cachedInlineCallbacks
|
||||||
from synapse.util.async import Linearizer
|
from synapse.util.async import Linearizer
|
||||||
from synapse.util.logcontext import preserve_fn
|
from synapse.util.logcontext import run_in_background
|
||||||
from synapse.util.logutils import log_function
|
from synapse.util.logutils import log_function
|
||||||
from synapse.util.metrics import Measure
|
from synapse.util.metrics import Measure
|
||||||
from synapse.util.wheel_timer import WheelTimer
|
from synapse.util.wheel_timer import WheelTimer
|
||||||
@@ -40,7 +42,6 @@ import synapse.metrics
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
metrics = synapse.metrics.get_metrics_for(__name__)
|
metrics = synapse.metrics.get_metrics_for(__name__)
|
||||||
@@ -87,6 +88,11 @@ assert LAST_ACTIVE_GRANULARITY < IDLE_TIMER
|
|||||||
class PresenceHandler(object):
|
class PresenceHandler(object):
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
|
"""
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hs (synapse.server.HomeServer):
|
||||||
|
"""
|
||||||
self.is_mine = hs.is_mine
|
self.is_mine = hs.is_mine
|
||||||
self.is_mine_id = hs.is_mine_id
|
self.is_mine_id = hs.is_mine_id
|
||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
@@ -94,7 +100,6 @@ class PresenceHandler(object):
|
|||||||
self.wheel_timer = WheelTimer()
|
self.wheel_timer = WheelTimer()
|
||||||
self.notifier = hs.get_notifier()
|
self.notifier = hs.get_notifier()
|
||||||
self.federation = hs.get_federation_sender()
|
self.federation = hs.get_federation_sender()
|
||||||
|
|
||||||
self.state = hs.get_state_handler()
|
self.state = hs.get_state_handler()
|
||||||
|
|
||||||
federation_registry = hs.get_federation_registry()
|
federation_registry = hs.get_federation_registry()
|
||||||
@@ -254,6 +259,14 @@ class PresenceHandler(object):
|
|||||||
|
|
||||||
logger.info("Finished _persist_unpersisted_changes")
|
logger.info("Finished _persist_unpersisted_changes")
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _update_states_and_catch_exception(self, new_states):
|
||||||
|
try:
|
||||||
|
res = yield self._update_states(new_states)
|
||||||
|
defer.returnValue(res)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error updating presence")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _update_states(self, new_states):
|
def _update_states(self, new_states):
|
||||||
"""Updates presence of users. Sets the appropriate timeouts. Pokes
|
"""Updates presence of users. Sets the appropriate timeouts. Pokes
|
||||||
@@ -364,7 +377,7 @@ class PresenceHandler(object):
|
|||||||
now=now,
|
now=now,
|
||||||
)
|
)
|
||||||
|
|
||||||
preserve_fn(self._update_states)(changes)
|
run_in_background(self._update_states_and_catch_exception, changes)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Exception in _handle_timeouts loop")
|
logger.exception("Exception in _handle_timeouts loop")
|
||||||
|
|
||||||
@@ -422,20 +435,23 @@ class PresenceHandler(object):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _end():
|
def _end():
|
||||||
if affect_presence:
|
try:
|
||||||
self.user_to_num_current_syncs[user_id] -= 1
|
self.user_to_num_current_syncs[user_id] -= 1
|
||||||
|
|
||||||
prev_state = yield self.current_state_for_user(user_id)
|
prev_state = yield self.current_state_for_user(user_id)
|
||||||
yield self._update_states([prev_state.copy_and_replace(
|
yield self._update_states([prev_state.copy_and_replace(
|
||||||
last_user_sync_ts=self.clock.time_msec(),
|
last_user_sync_ts=self.clock.time_msec(),
|
||||||
)])
|
)])
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error updating presence after sync")
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def _user_syncing():
|
def _user_syncing():
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
finally:
|
finally:
|
||||||
preserve_fn(_end)()
|
if affect_presence:
|
||||||
|
run_in_background(_end)
|
||||||
|
|
||||||
defer.returnValue(_user_syncing())
|
defer.returnValue(_user_syncing())
|
||||||
|
|
||||||
@@ -452,61 +468,6 @@ class PresenceHandler(object):
|
|||||||
syncing_user_ids.update(user_ids)
|
syncing_user_ids.update(user_ids)
|
||||||
return syncing_user_ids
|
return syncing_user_ids
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def update_external_syncs(self, process_id, syncing_user_ids):
|
|
||||||
"""Update the syncing users for an external process
|
|
||||||
|
|
||||||
Args:
|
|
||||||
process_id(str): An identifier for the process the users are
|
|
||||||
syncing against. This allows synapse to process updates
|
|
||||||
as user start and stop syncing against a given process.
|
|
||||||
syncing_user_ids(set(str)): The set of user_ids that are
|
|
||||||
currently syncing on that server.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Grab the previous list of user_ids that were syncing on that process
|
|
||||||
prev_syncing_user_ids = (
|
|
||||||
self.external_process_to_current_syncs.get(process_id, set())
|
|
||||||
)
|
|
||||||
# Grab the current presence state for both the users that are syncing
|
|
||||||
# now and the users that were syncing before this update.
|
|
||||||
prev_states = yield self.current_state_for_users(
|
|
||||||
syncing_user_ids | prev_syncing_user_ids
|
|
||||||
)
|
|
||||||
updates = []
|
|
||||||
time_now_ms = self.clock.time_msec()
|
|
||||||
|
|
||||||
# For each new user that is syncing check if we need to mark them as
|
|
||||||
# being online.
|
|
||||||
for new_user_id in syncing_user_ids - prev_syncing_user_ids:
|
|
||||||
prev_state = prev_states[new_user_id]
|
|
||||||
if prev_state.state == PresenceState.OFFLINE:
|
|
||||||
updates.append(prev_state.copy_and_replace(
|
|
||||||
state=PresenceState.ONLINE,
|
|
||||||
last_active_ts=time_now_ms,
|
|
||||||
last_user_sync_ts=time_now_ms,
|
|
||||||
))
|
|
||||||
else:
|
|
||||||
updates.append(prev_state.copy_and_replace(
|
|
||||||
last_user_sync_ts=time_now_ms,
|
|
||||||
))
|
|
||||||
|
|
||||||
# For each user that is still syncing or stopped syncing update the
|
|
||||||
# last sync time so that we will correctly apply the grace period when
|
|
||||||
# they stop syncing.
|
|
||||||
for old_user_id in prev_syncing_user_ids:
|
|
||||||
prev_state = prev_states[old_user_id]
|
|
||||||
updates.append(prev_state.copy_and_replace(
|
|
||||||
last_user_sync_ts=time_now_ms,
|
|
||||||
))
|
|
||||||
|
|
||||||
yield self._update_states(updates)
|
|
||||||
|
|
||||||
# Update the last updated time for the process. We expire the entries
|
|
||||||
# if we don't receive an update in the given timeframe.
|
|
||||||
self.external_process_last_updated_ms[process_id] = self.clock.time_msec()
|
|
||||||
self.external_process_to_current_syncs[process_id] = syncing_user_ids
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def update_external_syncs_row(self, process_id, user_id, is_syncing, sync_time_msec):
|
def update_external_syncs_row(self, process_id, user_id, is_syncing, sync_time_msec):
|
||||||
"""Update the syncing users for an external process as a delta.
|
"""Update the syncing users for an external process as a delta.
|
||||||
@@ -570,7 +531,7 @@ class PresenceHandler(object):
|
|||||||
prev_state.copy_and_replace(
|
prev_state.copy_and_replace(
|
||||||
last_user_sync_ts=time_now_ms,
|
last_user_sync_ts=time_now_ms,
|
||||||
)
|
)
|
||||||
for prev_state in prev_states.itervalues()
|
for prev_state in itervalues(prev_states)
|
||||||
])
|
])
|
||||||
self.external_process_last_updated_ms.pop(process_id, None)
|
self.external_process_last_updated_ms.pop(process_id, None)
|
||||||
|
|
||||||
@@ -593,14 +554,14 @@ class PresenceHandler(object):
|
|||||||
for user_id in user_ids
|
for user_id in user_ids
|
||||||
}
|
}
|
||||||
|
|
||||||
missing = [user_id for user_id, state in states.iteritems() if not state]
|
missing = [user_id for user_id, state in iteritems(states) if not state]
|
||||||
if missing:
|
if missing:
|
||||||
# There are things not in our in memory cache. Lets pull them out of
|
# There are things not in our in memory cache. Lets pull them out of
|
||||||
# the database.
|
# the database.
|
||||||
res = yield self.store.get_presence_for_users(missing)
|
res = yield self.store.get_presence_for_users(missing)
|
||||||
states.update(res)
|
states.update(res)
|
||||||
|
|
||||||
missing = [user_id for user_id, state in states.iteritems() if not state]
|
missing = [user_id for user_id, state in iteritems(states) if not state]
|
||||||
if missing:
|
if missing:
|
||||||
new = {
|
new = {
|
||||||
user_id: UserPresenceState.default(user_id)
|
user_id: UserPresenceState.default(user_id)
|
||||||
@@ -1088,7 +1049,7 @@ class PresenceEventSource(object):
|
|||||||
defer.returnValue((updates.values(), max_token))
|
defer.returnValue((updates.values(), max_token))
|
||||||
else:
|
else:
|
||||||
defer.returnValue(([
|
defer.returnValue(([
|
||||||
s for s in updates.itervalues()
|
s for s in itervalues(updates)
|
||||||
if s.state != PresenceState.OFFLINE
|
if s.state != PresenceState.OFFLINE
|
||||||
], max_token))
|
], max_token))
|
||||||
|
|
||||||
@@ -1345,11 +1306,11 @@ def get_interested_remotes(store, states, state_handler):
|
|||||||
# hosts in those rooms.
|
# hosts in those rooms.
|
||||||
room_ids_to_states, users_to_states = yield get_interested_parties(store, states)
|
room_ids_to_states, users_to_states = yield get_interested_parties(store, states)
|
||||||
|
|
||||||
for room_id, states in room_ids_to_states.iteritems():
|
for room_id, states in iteritems(room_ids_to_states):
|
||||||
hosts = yield state_handler.get_current_hosts_in_room(room_id)
|
hosts = yield state_handler.get_current_hosts_in_room(room_id)
|
||||||
hosts_and_states.append((hosts, states))
|
hosts_and_states.append((hosts, states))
|
||||||
|
|
||||||
for user_id, states in users_to_states.iteritems():
|
for user_id, states in iteritems(users_to_states):
|
||||||
host = get_domain_from_id(user_id)
|
host = get_domain_from_id(user_id)
|
||||||
hosts_and_states.append(([host], states))
|
hosts_and_states.append(([host], states))
|
||||||
|
|
||||||
|
|||||||
@@ -135,37 +135,40 @@ class ReceiptsHandler(BaseHandler):
|
|||||||
"""Given a list of receipts, works out which remote servers should be
|
"""Given a list of receipts, works out which remote servers should be
|
||||||
poked and pokes them.
|
poked and pokes them.
|
||||||
"""
|
"""
|
||||||
# TODO: Some of this stuff should be coallesced.
|
try:
|
||||||
for receipt in receipts:
|
# TODO: Some of this stuff should be coallesced.
|
||||||
room_id = receipt["room_id"]
|
for receipt in receipts:
|
||||||
receipt_type = receipt["receipt_type"]
|
room_id = receipt["room_id"]
|
||||||
user_id = receipt["user_id"]
|
receipt_type = receipt["receipt_type"]
|
||||||
event_ids = receipt["event_ids"]
|
user_id = receipt["user_id"]
|
||||||
data = receipt["data"]
|
event_ids = receipt["event_ids"]
|
||||||
|
data = receipt["data"]
|
||||||
|
|
||||||
users = yield self.state.get_current_user_in_room(room_id)
|
users = yield self.state.get_current_user_in_room(room_id)
|
||||||
remotedomains = set(get_domain_from_id(u) for u in users)
|
remotedomains = set(get_domain_from_id(u) for u in users)
|
||||||
remotedomains = remotedomains.copy()
|
remotedomains = remotedomains.copy()
|
||||||
remotedomains.discard(self.server_name)
|
remotedomains.discard(self.server_name)
|
||||||
|
|
||||||
logger.debug("Sending receipt to: %r", remotedomains)
|
logger.debug("Sending receipt to: %r", remotedomains)
|
||||||
|
|
||||||
for domain in remotedomains:
|
for domain in remotedomains:
|
||||||
self.federation.send_edu(
|
self.federation.send_edu(
|
||||||
destination=domain,
|
destination=domain,
|
||||||
edu_type="m.receipt",
|
edu_type="m.receipt",
|
||||||
content={
|
content={
|
||||||
room_id: {
|
room_id: {
|
||||||
receipt_type: {
|
receipt_type: {
|
||||||
user_id: {
|
user_id: {
|
||||||
"event_ids": event_ids,
|
"event_ids": event_ids,
|
||||||
"data": data,
|
"data": data,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
},
|
key=(room_id, receipt_type, user_id),
|
||||||
key=(room_id, receipt_type, user_id),
|
)
|
||||||
)
|
except Exception:
|
||||||
|
logger.exception("Error pushing receipts to remote servers")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_receipts_for_room(self, room_id, to_key):
|
def get_receipts_for_room(self, room_id, to_key):
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ logger = logging.getLogger(__name__)
|
|||||||
class RegistrationHandler(BaseHandler):
|
class RegistrationHandler(BaseHandler):
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
|
"""
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hs (synapse.server.HomeServer):
|
||||||
|
"""
|
||||||
super(RegistrationHandler, self).__init__(hs)
|
super(RegistrationHandler, self).__init__(hs)
|
||||||
|
|
||||||
self.auth = hs.get_auth()
|
self.auth = hs.get_auth()
|
||||||
@@ -49,6 +54,7 @@ class RegistrationHandler(BaseHandler):
|
|||||||
self._generate_user_id_linearizer = Linearizer(
|
self._generate_user_id_linearizer = Linearizer(
|
||||||
name="_generate_user_id_linearizer",
|
name="_generate_user_id_linearizer",
|
||||||
)
|
)
|
||||||
|
self._server_notices_mxid = hs.config.server_notices_mxid
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def check_username(self, localpart, guest_access_token=None,
|
def check_username(self, localpart, guest_access_token=None,
|
||||||
@@ -338,6 +344,14 @@ class RegistrationHandler(BaseHandler):
|
|||||||
yield identity_handler.bind_threepid(c, user_id)
|
yield identity_handler.bind_threepid(c, user_id)
|
||||||
|
|
||||||
def check_user_id_not_appservice_exclusive(self, user_id, allowed_appservice=None):
|
def check_user_id_not_appservice_exclusive(self, user_id, allowed_appservice=None):
|
||||||
|
# don't allow people to register the server notices mxid
|
||||||
|
if self._server_notices_mxid is not None:
|
||||||
|
if user_id == self._server_notices_mxid:
|
||||||
|
raise SynapseError(
|
||||||
|
400, "This user ID is reserved.",
|
||||||
|
errcode=Codes.EXCLUSIVE
|
||||||
|
)
|
||||||
|
|
||||||
# valid user IDs must not clash with any user ID namespaces claimed by
|
# valid user IDs must not clash with any user ID namespaces claimed by
|
||||||
# application services.
|
# application services.
|
||||||
services = self.store.get_app_services()
|
services = self.store.get_app_services()
|
||||||
|
|||||||
@@ -68,14 +68,27 @@ class RoomCreationHandler(BaseHandler):
|
|||||||
self.event_creation_handler = hs.get_event_creation_handler()
|
self.event_creation_handler = hs.get_event_creation_handler()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def create_room(self, requester, config, ratelimit=True):
|
def create_room(self, requester, config, ratelimit=True,
|
||||||
|
creator_join_profile=None):
|
||||||
""" Creates a new room.
|
""" Creates a new room.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
requester (Requester): The user who requested the room creation.
|
requester (synapse.types.Requester):
|
||||||
|
The user who requested the room creation.
|
||||||
config (dict) : A dict of configuration options.
|
config (dict) : A dict of configuration options.
|
||||||
|
ratelimit (bool): set to False to disable the rate limiter
|
||||||
|
|
||||||
|
creator_join_profile (dict|None):
|
||||||
|
Set to override the displayname and avatar for the creating
|
||||||
|
user in this room. If unset, displayname and avatar will be
|
||||||
|
derived from the user's profile. If set, should contain the
|
||||||
|
values to go in the body of the 'join' event (typically
|
||||||
|
`avatar_url` and/or `displayname`.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The new room ID.
|
Deferred[dict]:
|
||||||
|
a dict containing the keys `room_id` and, if an alias was
|
||||||
|
requested, `room_alias`.
|
||||||
Raises:
|
Raises:
|
||||||
SynapseError if the room ID couldn't be stored, or something went
|
SynapseError if the room ID couldn't be stored, or something went
|
||||||
horribly wrong.
|
horribly wrong.
|
||||||
@@ -113,6 +126,10 @@ class RoomCreationHandler(BaseHandler):
|
|||||||
except Exception:
|
except Exception:
|
||||||
raise SynapseError(400, "Invalid user_id: %s" % (i,))
|
raise SynapseError(400, "Invalid user_id: %s" % (i,))
|
||||||
|
|
||||||
|
yield self.event_creation_handler.assert_accepted_privacy_policy(
|
||||||
|
requester,
|
||||||
|
)
|
||||||
|
|
||||||
invite_3pid_list = config.get("invite_3pid", [])
|
invite_3pid_list = config.get("invite_3pid", [])
|
||||||
|
|
||||||
visibility = config.get("visibility", None)
|
visibility = config.get("visibility", None)
|
||||||
@@ -176,7 +193,8 @@ class RoomCreationHandler(BaseHandler):
|
|||||||
initial_state=initial_state,
|
initial_state=initial_state,
|
||||||
creation_content=creation_content,
|
creation_content=creation_content,
|
||||||
room_alias=room_alias,
|
room_alias=room_alias,
|
||||||
power_level_content_override=config.get("power_level_content_override", {})
|
power_level_content_override=config.get("power_level_content_override", {}),
|
||||||
|
creator_join_profile=creator_join_profile,
|
||||||
)
|
)
|
||||||
|
|
||||||
if "name" in config:
|
if "name" in config:
|
||||||
@@ -256,6 +274,7 @@ class RoomCreationHandler(BaseHandler):
|
|||||||
creation_content,
|
creation_content,
|
||||||
room_alias,
|
room_alias,
|
||||||
power_level_content_override,
|
power_level_content_override,
|
||||||
|
creator_join_profile,
|
||||||
):
|
):
|
||||||
def create(etype, content, **kwargs):
|
def create(etype, content, **kwargs):
|
||||||
e = {
|
e = {
|
||||||
@@ -299,6 +318,7 @@ class RoomCreationHandler(BaseHandler):
|
|||||||
room_id,
|
room_id,
|
||||||
"join",
|
"join",
|
||||||
ratelimit=False,
|
ratelimit=False,
|
||||||
|
content=creator_join_profile,
|
||||||
)
|
)
|
||||||
|
|
||||||
# We treat the power levels override specially as this needs to be one
|
# We treat the power levels override specially as this needs to be one
|
||||||
@@ -514,7 +534,7 @@ class RoomEventSource(object):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_pagination_rows(self, user, config, key):
|
def get_pagination_rows(self, user, config, key):
|
||||||
events, next_key = yield self.store.paginate_room_events(
|
events, next_key, _ = yield self.store.paginate_room_events(
|
||||||
room_id=key,
|
room_id=key,
|
||||||
from_key=config.from_key,
|
from_key=config.from_key,
|
||||||
to_key=config.to_key,
|
to_key=config.to_key,
|
||||||
|
|||||||
@@ -15,12 +15,13 @@
|
|||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from six.moves import range
|
||||||
|
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
|
|
||||||
from synapse.api.constants import (
|
from synapse.api.constants import (
|
||||||
EventTypes, JoinRules,
|
EventTypes, JoinRules,
|
||||||
)
|
)
|
||||||
from synapse.util.logcontext import make_deferred_yieldable, preserve_fn
|
|
||||||
from synapse.util.async import concurrently_execute
|
from synapse.util.async import concurrently_execute
|
||||||
from synapse.util.caches.descriptors import cachedInlineCallbacks
|
from synapse.util.caches.descriptors import cachedInlineCallbacks
|
||||||
from synapse.util.caches.response_cache import ResponseCache
|
from synapse.util.caches.response_cache import ResponseCache
|
||||||
@@ -78,18 +79,11 @@ class RoomListHandler(BaseHandler):
|
|||||||
)
|
)
|
||||||
|
|
||||||
key = (limit, since_token, network_tuple)
|
key = (limit, since_token, network_tuple)
|
||||||
result = self.response_cache.get(key)
|
return self.response_cache.wrap(
|
||||||
if not result:
|
key,
|
||||||
logger.info("No cached result, calculating one.")
|
self._get_public_room_list,
|
||||||
result = self.response_cache.set(
|
limit, since_token, network_tuple=network_tuple,
|
||||||
key,
|
)
|
||||||
preserve_fn(self._get_public_room_list)(
|
|
||||||
limit, since_token, network_tuple=network_tuple
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.info("Using cached deferred result.")
|
|
||||||
return make_deferred_yieldable(result)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _get_public_room_list(self, limit=None, since_token=None,
|
def _get_public_room_list(self, limit=None, since_token=None,
|
||||||
@@ -208,7 +202,7 @@ class RoomListHandler(BaseHandler):
|
|||||||
step = len(rooms_to_scan) if len(rooms_to_scan) != 0 else 1
|
step = len(rooms_to_scan) if len(rooms_to_scan) != 0 else 1
|
||||||
|
|
||||||
chunk = []
|
chunk = []
|
||||||
for i in xrange(0, len(rooms_to_scan), step):
|
for i in range(0, len(rooms_to_scan), step):
|
||||||
batch = rooms_to_scan[i:i + step]
|
batch = rooms_to_scan[i:i + step]
|
||||||
logger.info("Processing %i rooms for result", len(batch))
|
logger.info("Processing %i rooms for result", len(batch))
|
||||||
yield concurrently_execute(
|
yield concurrently_execute(
|
||||||
@@ -423,18 +417,14 @@ class RoomListHandler(BaseHandler):
|
|||||||
server_name, limit, since_token, include_all_networks,
|
server_name, limit, since_token, include_all_networks,
|
||||||
third_party_instance_id,
|
third_party_instance_id,
|
||||||
)
|
)
|
||||||
result = self.remote_response_cache.get(key)
|
return self.remote_response_cache.wrap(
|
||||||
if not result:
|
key,
|
||||||
result = self.remote_response_cache.set(
|
repl_layer.get_public_rooms,
|
||||||
key,
|
server_name, limit=limit, since_token=since_token,
|
||||||
repl_layer.get_public_rooms(
|
search_filter=search_filter,
|
||||||
server_name, limit=limit, since_token=since_token,
|
include_all_networks=include_all_networks,
|
||||||
search_filter=search_filter,
|
third_party_instance_id=third_party_instance_id,
|
||||||
include_all_networks=include_all_networks,
|
)
|
||||||
third_party_instance_id=third_party_instance_id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class RoomListNextBatch(namedtuple("RoomListNextBatch", (
|
class RoomListNextBatch(namedtuple("RoomListNextBatch", (
|
||||||
|
|||||||
@@ -17,11 +17,14 @@
|
|||||||
import abc
|
import abc
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from six.moves import http_client
|
||||||
|
|
||||||
from signedjson.key import decode_verify_key_bytes
|
from signedjson.key import decode_verify_key_bytes
|
||||||
from signedjson.sign import verify_signed_json
|
from signedjson.sign import verify_signed_json
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
from unpaddedbase64 import decode_base64
|
from unpaddedbase64 import decode_base64
|
||||||
|
|
||||||
|
import synapse.server
|
||||||
import synapse.types
|
import synapse.types
|
||||||
from synapse.api.constants import (
|
from synapse.api.constants import (
|
||||||
EventTypes, Membership,
|
EventTypes, Membership,
|
||||||
@@ -46,6 +49,11 @@ class RoomMemberHandler(object):
|
|||||||
__metaclass__ = abc.ABCMeta
|
__metaclass__ = abc.ABCMeta
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
|
"""
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hs (synapse.server.HomeServer):
|
||||||
|
"""
|
||||||
self.hs = hs
|
self.hs = hs
|
||||||
self.store = hs.get_datastore()
|
self.store = hs.get_datastore()
|
||||||
self.auth = hs.get_auth()
|
self.auth = hs.get_auth()
|
||||||
@@ -63,6 +71,7 @@ class RoomMemberHandler(object):
|
|||||||
|
|
||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
self.spam_checker = hs.get_spam_checker()
|
self.spam_checker = hs.get_spam_checker()
|
||||||
|
self._server_notices_mxid = self.config.server_notices_mxid
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def _remote_join(self, requester, remote_room_hosts, room_id, user, content):
|
def _remote_join(self, requester, remote_room_hosts, room_id, user, content):
|
||||||
@@ -149,7 +158,7 @@ class RoomMemberHandler(object):
|
|||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _local_membership_update(
|
def _local_membership_update(
|
||||||
self, requester, target, room_id, membership,
|
self, requester, target, room_id, membership,
|
||||||
prev_event_ids,
|
prev_events_and_hashes,
|
||||||
txn_id=None,
|
txn_id=None,
|
||||||
ratelimit=True,
|
ratelimit=True,
|
||||||
content=None,
|
content=None,
|
||||||
@@ -175,7 +184,7 @@ class RoomMemberHandler(object):
|
|||||||
},
|
},
|
||||||
token_id=requester.access_token_id,
|
token_id=requester.access_token_id,
|
||||||
txn_id=txn_id,
|
txn_id=txn_id,
|
||||||
prev_event_ids=prev_event_ids,
|
prev_events_and_hashes=prev_events_and_hashes,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if this event matches the previous membership event for the user.
|
# Check if this event matches the previous membership event for the user.
|
||||||
@@ -289,12 +298,37 @@ class RoomMemberHandler(object):
|
|||||||
is_blocked = yield self.store.is_room_blocked(room_id)
|
is_blocked = yield self.store.is_room_blocked(room_id)
|
||||||
if is_blocked:
|
if is_blocked:
|
||||||
raise SynapseError(403, "This room has been blocked on this server")
|
raise SynapseError(403, "This room has been blocked on this server")
|
||||||
|
else:
|
||||||
|
# we don't allow people to reject invites to, or leave, the
|
||||||
|
# server notice room.
|
||||||
|
is_blocked = yield self._is_server_notice_room(room_id)
|
||||||
|
if is_blocked:
|
||||||
|
raise SynapseError(
|
||||||
|
http_client.FORBIDDEN,
|
||||||
|
"You cannot leave this room",
|
||||||
|
errcode=Codes.CANNOT_LEAVE_SERVER_NOTICE_ROOM,
|
||||||
|
)
|
||||||
|
|
||||||
|
if effective_membership_state == Membership.INVITE:
|
||||||
|
# block any attempts to invite the server notices mxid
|
||||||
|
if target.to_string() == self._server_notices_mxid:
|
||||||
|
raise SynapseError(
|
||||||
|
http_client.FORBIDDEN,
|
||||||
|
"Cannot invite this user",
|
||||||
|
)
|
||||||
|
|
||||||
if effective_membership_state == "invite":
|
|
||||||
block_invite = False
|
block_invite = False
|
||||||
is_requester_admin = yield self.auth.is_server_admin(
|
|
||||||
requester.user,
|
if (self._server_notices_mxid is not None and
|
||||||
)
|
requester.user.to_string() == self._server_notices_mxid):
|
||||||
|
# allow the server notices mxid to send invites
|
||||||
|
is_requester_admin = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
is_requester_admin = yield self.auth.is_server_admin(
|
||||||
|
requester.user,
|
||||||
|
)
|
||||||
|
|
||||||
if not is_requester_admin:
|
if not is_requester_admin:
|
||||||
if self.config.block_non_admin_invites:
|
if self.config.block_non_admin_invites:
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -314,7 +348,12 @@ class RoomMemberHandler(object):
|
|||||||
403, "Invites have been disabled on this server",
|
403, "Invites have been disabled on this server",
|
||||||
)
|
)
|
||||||
|
|
||||||
latest_event_ids = yield self.store.get_latest_event_ids_in_room(room_id)
|
prev_events_and_hashes = yield self.store.get_prev_events_for_room(
|
||||||
|
room_id,
|
||||||
|
)
|
||||||
|
latest_event_ids = (
|
||||||
|
event_id for (event_id, _, _) in prev_events_and_hashes
|
||||||
|
)
|
||||||
current_state_ids = yield self.state_handler.get_current_state_ids(
|
current_state_ids = yield self.state_handler.get_current_state_ids(
|
||||||
room_id, latest_event_ids=latest_event_ids,
|
room_id, latest_event_ids=latest_event_ids,
|
||||||
)
|
)
|
||||||
@@ -403,7 +442,7 @@ class RoomMemberHandler(object):
|
|||||||
membership=effective_membership_state,
|
membership=effective_membership_state,
|
||||||
txn_id=txn_id,
|
txn_id=txn_id,
|
||||||
ratelimit=ratelimit,
|
ratelimit=ratelimit,
|
||||||
prev_event_ids=latest_event_ids,
|
prev_events_and_hashes=prev_events_and_hashes,
|
||||||
content=content,
|
content=content,
|
||||||
)
|
)
|
||||||
defer.returnValue(res)
|
defer.returnValue(res)
|
||||||
@@ -839,6 +878,13 @@ class RoomMemberHandler(object):
|
|||||||
|
|
||||||
defer.returnValue(False)
|
defer.returnValue(False)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _is_server_notice_room(self, room_id):
|
||||||
|
if self._server_notices_mxid is None:
|
||||||
|
defer.returnValue(False)
|
||||||
|
user_ids = yield self.store.get_users_in_room(room_id)
|
||||||
|
defer.returnValue(self._server_notices_mxid in user_ids)
|
||||||
|
|
||||||
|
|
||||||
class RoomMemberMasterHandler(RoomMemberHandler):
|
class RoomMemberMasterHandler(RoomMemberHandler):
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
from synapse.api.constants import Membership, EventTypes
|
from synapse.api.constants import Membership, EventTypes
|
||||||
from synapse.util.async import concurrently_execute
|
from synapse.util.async import concurrently_execute
|
||||||
from synapse.util.logcontext import LoggingContext, make_deferred_yieldable, preserve_fn
|
from synapse.util.logcontext import LoggingContext
|
||||||
from synapse.util.metrics import Measure, measure_func
|
from synapse.util.metrics import Measure, measure_func
|
||||||
from synapse.util.caches.response_cache import ResponseCache
|
from synapse.util.caches.response_cache import ResponseCache
|
||||||
from synapse.push.clientformat import format_push_rules_for_user
|
from synapse.push.clientformat import format_push_rules_for_user
|
||||||
@@ -28,6 +28,8 @@ import collections
|
|||||||
import logging
|
import logging
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
|
from six import itervalues, iteritems
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -52,6 +54,7 @@ class TimelineBatch(collections.namedtuple("TimelineBatch", [
|
|||||||
to tell if room needs to be part of the sync result.
|
to tell if room needs to be part of the sync result.
|
||||||
"""
|
"""
|
||||||
return bool(self.events)
|
return bool(self.events)
|
||||||
|
__bool__ = __nonzero__ # python3
|
||||||
|
|
||||||
|
|
||||||
class JoinedSyncResult(collections.namedtuple("JoinedSyncResult", [
|
class JoinedSyncResult(collections.namedtuple("JoinedSyncResult", [
|
||||||
@@ -76,6 +79,7 @@ class JoinedSyncResult(collections.namedtuple("JoinedSyncResult", [
|
|||||||
# nb the notification count does not, er, count: if there's nothing
|
# nb the notification count does not, er, count: if there's nothing
|
||||||
# else in the result, we don't need to send it.
|
# else in the result, we don't need to send it.
|
||||||
)
|
)
|
||||||
|
__bool__ = __nonzero__ # python3
|
||||||
|
|
||||||
|
|
||||||
class ArchivedSyncResult(collections.namedtuple("ArchivedSyncResult", [
|
class ArchivedSyncResult(collections.namedtuple("ArchivedSyncResult", [
|
||||||
@@ -95,6 +99,7 @@ class ArchivedSyncResult(collections.namedtuple("ArchivedSyncResult", [
|
|||||||
or self.state
|
or self.state
|
||||||
or self.account_data
|
or self.account_data
|
||||||
)
|
)
|
||||||
|
__bool__ = __nonzero__ # python3
|
||||||
|
|
||||||
|
|
||||||
class InvitedSyncResult(collections.namedtuple("InvitedSyncResult", [
|
class InvitedSyncResult(collections.namedtuple("InvitedSyncResult", [
|
||||||
@@ -106,6 +111,7 @@ class InvitedSyncResult(collections.namedtuple("InvitedSyncResult", [
|
|||||||
def __nonzero__(self):
|
def __nonzero__(self):
|
||||||
"""Invited rooms should always be reported to the client"""
|
"""Invited rooms should always be reported to the client"""
|
||||||
return True
|
return True
|
||||||
|
__bool__ = __nonzero__ # python3
|
||||||
|
|
||||||
|
|
||||||
class GroupsSyncResult(collections.namedtuple("GroupsSyncResult", [
|
class GroupsSyncResult(collections.namedtuple("GroupsSyncResult", [
|
||||||
@@ -117,6 +123,7 @@ class GroupsSyncResult(collections.namedtuple("GroupsSyncResult", [
|
|||||||
|
|
||||||
def __nonzero__(self):
|
def __nonzero__(self):
|
||||||
return bool(self.join or self.invite or self.leave)
|
return bool(self.join or self.invite or self.leave)
|
||||||
|
__bool__ = __nonzero__ # python3
|
||||||
|
|
||||||
|
|
||||||
class DeviceLists(collections.namedtuple("DeviceLists", [
|
class DeviceLists(collections.namedtuple("DeviceLists", [
|
||||||
@@ -127,6 +134,7 @@ class DeviceLists(collections.namedtuple("DeviceLists", [
|
|||||||
|
|
||||||
def __nonzero__(self):
|
def __nonzero__(self):
|
||||||
return bool(self.changed or self.left)
|
return bool(self.changed or self.left)
|
||||||
|
__bool__ = __nonzero__ # python3
|
||||||
|
|
||||||
|
|
||||||
class SyncResult(collections.namedtuple("SyncResult", [
|
class SyncResult(collections.namedtuple("SyncResult", [
|
||||||
@@ -159,6 +167,7 @@ class SyncResult(collections.namedtuple("SyncResult", [
|
|||||||
self.device_lists or
|
self.device_lists or
|
||||||
self.groups
|
self.groups
|
||||||
)
|
)
|
||||||
|
__bool__ = __nonzero__ # python3
|
||||||
|
|
||||||
|
|
||||||
class SyncHandler(object):
|
class SyncHandler(object):
|
||||||
@@ -180,15 +189,11 @@ class SyncHandler(object):
|
|||||||
Returns:
|
Returns:
|
||||||
A Deferred SyncResult.
|
A Deferred SyncResult.
|
||||||
"""
|
"""
|
||||||
result = self.response_cache.get(sync_config.request_key)
|
return self.response_cache.wrap(
|
||||||
if not result:
|
sync_config.request_key,
|
||||||
result = self.response_cache.set(
|
self._wait_for_sync_for_user,
|
||||||
sync_config.request_key,
|
sync_config, since_token, timeout, full_state,
|
||||||
preserve_fn(self._wait_for_sync_for_user)(
|
)
|
||||||
sync_config, since_token, timeout, full_state
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return make_deferred_yieldable(result)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _wait_for_sync_for_user(self, sync_config, since_token, timeout,
|
def _wait_for_sync_for_user(self, sync_config, since_token, timeout,
|
||||||
@@ -272,7 +277,7 @@ class SyncHandler(object):
|
|||||||
# result returned by the event source is poor form (it might cache
|
# result returned by the event source is poor form (it might cache
|
||||||
# the object)
|
# the object)
|
||||||
room_id = event["room_id"]
|
room_id = event["room_id"]
|
||||||
event_copy = {k: v for (k, v) in event.iteritems()
|
event_copy = {k: v for (k, v) in iteritems(event)
|
||||||
if k != "room_id"}
|
if k != "room_id"}
|
||||||
ephemeral_by_room.setdefault(room_id, []).append(event_copy)
|
ephemeral_by_room.setdefault(room_id, []).append(event_copy)
|
||||||
|
|
||||||
@@ -291,7 +296,7 @@ class SyncHandler(object):
|
|||||||
for event in receipts:
|
for event in receipts:
|
||||||
room_id = event["room_id"]
|
room_id = event["room_id"]
|
||||||
# exclude room id, as above
|
# exclude room id, as above
|
||||||
event_copy = {k: v for (k, v) in event.iteritems()
|
event_copy = {k: v for (k, v) in iteritems(event)
|
||||||
if k != "room_id"}
|
if k != "room_id"}
|
||||||
ephemeral_by_room.setdefault(room_id, []).append(event_copy)
|
ephemeral_by_room.setdefault(room_id, []).append(event_copy)
|
||||||
|
|
||||||
@@ -322,7 +327,7 @@ class SyncHandler(object):
|
|||||||
current_state_ids = frozenset()
|
current_state_ids = frozenset()
|
||||||
if any(e.is_state() for e in recents):
|
if any(e.is_state() for e in recents):
|
||||||
current_state_ids = yield self.state.get_current_state_ids(room_id)
|
current_state_ids = yield self.state.get_current_state_ids(room_id)
|
||||||
current_state_ids = frozenset(current_state_ids.itervalues())
|
current_state_ids = frozenset(itervalues(current_state_ids))
|
||||||
|
|
||||||
recents = yield filter_events_for_client(
|
recents = yield filter_events_for_client(
|
||||||
self.store,
|
self.store,
|
||||||
@@ -351,12 +356,24 @@ class SyncHandler(object):
|
|||||||
since_key = since_token.room_key
|
since_key = since_token.room_key
|
||||||
|
|
||||||
while limited and len(recents) < timeline_limit and max_repeat:
|
while limited and len(recents) < timeline_limit and max_repeat:
|
||||||
events, end_key = yield self.store.get_room_events_stream_for_room(
|
# If we have a since_key then we are trying to get any events
|
||||||
room_id,
|
# that have happened since `since_key` up to `end_key`, so we
|
||||||
limit=load_limit + 1,
|
# can just use `get_room_events_stream_for_room`.
|
||||||
from_key=since_key,
|
# Otherwise, we want to return the last N events in the room
|
||||||
to_key=end_key,
|
# in toplogical ordering.
|
||||||
)
|
if since_key:
|
||||||
|
events, end_key = yield self.store.get_room_events_stream_for_room(
|
||||||
|
room_id,
|
||||||
|
limit=load_limit + 1,
|
||||||
|
from_key=since_key,
|
||||||
|
to_key=end_key,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
events, end_key = yield self.store.get_recent_events_for_room(
|
||||||
|
room_id,
|
||||||
|
limit=load_limit + 1,
|
||||||
|
end_token=end_key,
|
||||||
|
)
|
||||||
loaded_recents = sync_config.filter_collection.filter_room_timeline(
|
loaded_recents = sync_config.filter_collection.filter_room_timeline(
|
||||||
events
|
events
|
||||||
)
|
)
|
||||||
@@ -367,7 +384,7 @@ class SyncHandler(object):
|
|||||||
current_state_ids = frozenset()
|
current_state_ids = frozenset()
|
||||||
if any(e.is_state() for e in loaded_recents):
|
if any(e.is_state() for e in loaded_recents):
|
||||||
current_state_ids = yield self.state.get_current_state_ids(room_id)
|
current_state_ids = yield self.state.get_current_state_ids(room_id)
|
||||||
current_state_ids = frozenset(current_state_ids.itervalues())
|
current_state_ids = frozenset(itervalues(current_state_ids))
|
||||||
|
|
||||||
loaded_recents = yield filter_events_for_client(
|
loaded_recents = yield filter_events_for_client(
|
||||||
self.store,
|
self.store,
|
||||||
@@ -426,7 +443,7 @@ class SyncHandler(object):
|
|||||||
Returns:
|
Returns:
|
||||||
A Deferred map from ((type, state_key)->Event)
|
A Deferred map from ((type, state_key)->Event)
|
||||||
"""
|
"""
|
||||||
last_events, token = yield self.store.get_recent_events_for_room(
|
last_events, _ = yield self.store.get_recent_events_for_room(
|
||||||
room_id, end_token=stream_position.room_key, limit=1,
|
room_id, end_token=stream_position.room_key, limit=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -969,7 +986,7 @@ class SyncHandler(object):
|
|||||||
if since_token:
|
if since_token:
|
||||||
for joined_sync in sync_result_builder.joined:
|
for joined_sync in sync_result_builder.joined:
|
||||||
it = itertools.chain(
|
it = itertools.chain(
|
||||||
joined_sync.timeline.events, joined_sync.state.itervalues()
|
joined_sync.timeline.events, itervalues(joined_sync.state)
|
||||||
)
|
)
|
||||||
for event in it:
|
for event in it:
|
||||||
if event.type == EventTypes.Member:
|
if event.type == EventTypes.Member:
|
||||||
@@ -1047,7 +1064,7 @@ class SyncHandler(object):
|
|||||||
newly_left_rooms = []
|
newly_left_rooms = []
|
||||||
room_entries = []
|
room_entries = []
|
||||||
invited = []
|
invited = []
|
||||||
for room_id, events in mem_change_events_by_room_id.iteritems():
|
for room_id, events in iteritems(mem_change_events_by_room_id):
|
||||||
non_joins = [e for e in events if e.membership != Membership.JOIN]
|
non_joins = [e for e in events if e.membership != Membership.JOIN]
|
||||||
has_join = len(non_joins) != len(events)
|
has_join = len(non_joins) != len(events)
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.api.errors import SynapseError, AuthError
|
from synapse.api.errors import SynapseError, AuthError
|
||||||
from synapse.util.logcontext import preserve_fn
|
from synapse.util.logcontext import run_in_background
|
||||||
from synapse.util.metrics import Measure
|
from synapse.util.metrics import Measure
|
||||||
from synapse.util.wheel_timer import WheelTimer
|
from synapse.util.wheel_timer import WheelTimer
|
||||||
from synapse.types import UserID, get_domain_from_id
|
from synapse.types import UserID, get_domain_from_id
|
||||||
@@ -97,7 +97,8 @@ class TypingHandler(object):
|
|||||||
if self.hs.is_mine_id(member.user_id):
|
if self.hs.is_mine_id(member.user_id):
|
||||||
last_fed_poke = self._member_last_federation_poke.get(member, None)
|
last_fed_poke = self._member_last_federation_poke.get(member, None)
|
||||||
if not last_fed_poke or last_fed_poke + FEDERATION_PING_INTERVAL <= now:
|
if not last_fed_poke or last_fed_poke + FEDERATION_PING_INTERVAL <= now:
|
||||||
preserve_fn(self._push_remote)(
|
run_in_background(
|
||||||
|
self._push_remote,
|
||||||
member=member,
|
member=member,
|
||||||
typing=True
|
typing=True
|
||||||
)
|
)
|
||||||
@@ -196,7 +197,7 @@ class TypingHandler(object):
|
|||||||
def _push_update(self, member, typing):
|
def _push_update(self, member, typing):
|
||||||
if self.hs.is_mine_id(member.user_id):
|
if self.hs.is_mine_id(member.user_id):
|
||||||
# Only send updates for changes to our own users.
|
# Only send updates for changes to our own users.
|
||||||
preserve_fn(self._push_remote)(member, typing)
|
run_in_background(self._push_remote, member, typing)
|
||||||
|
|
||||||
self._push_update_local(
|
self._push_update_local(
|
||||||
member=member,
|
member=member,
|
||||||
@@ -205,28 +206,31 @@ class TypingHandler(object):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _push_remote(self, member, typing):
|
def _push_remote(self, member, typing):
|
||||||
users = yield self.state.get_current_user_in_room(member.room_id)
|
try:
|
||||||
self._member_last_federation_poke[member] = self.clock.time_msec()
|
users = yield self.state.get_current_user_in_room(member.room_id)
|
||||||
|
self._member_last_federation_poke[member] = self.clock.time_msec()
|
||||||
|
|
||||||
now = self.clock.time_msec()
|
now = self.clock.time_msec()
|
||||||
self.wheel_timer.insert(
|
self.wheel_timer.insert(
|
||||||
now=now,
|
now=now,
|
||||||
obj=member,
|
obj=member,
|
||||||
then=now + FEDERATION_PING_INTERVAL,
|
then=now + FEDERATION_PING_INTERVAL,
|
||||||
)
|
)
|
||||||
|
|
||||||
for domain in set(get_domain_from_id(u) for u in users):
|
for domain in set(get_domain_from_id(u) for u in users):
|
||||||
if domain != self.server_name:
|
if domain != self.server_name:
|
||||||
self.federation.send_edu(
|
self.federation.send_edu(
|
||||||
destination=domain,
|
destination=domain,
|
||||||
edu_type="m.typing",
|
edu_type="m.typing",
|
||||||
content={
|
content={
|
||||||
"room_id": member.room_id,
|
"room_id": member.room_id,
|
||||||
"user_id": member.user_id,
|
"user_id": member.user_id,
|
||||||
"typing": typing,
|
"typing": typing,
|
||||||
},
|
},
|
||||||
key=member,
|
key=member,
|
||||||
)
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error pushing typing notif to remotes")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _recv_edu(self, origin, content):
|
def _recv_edu(self, origin, content):
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from synapse.util.metrics import Measure
|
|||||||
from synapse.util.async import sleep
|
from synapse.util.async import sleep
|
||||||
from synapse.types import get_localpart_from_id
|
from synapse.types import get_localpart_from_id
|
||||||
|
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -122,6 +123,13 @@ class UserDirectoryHandler(object):
|
|||||||
user_id, profile.display_name, profile.avatar_url, None,
|
user_id, profile.display_name, profile.avatar_url, None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def handle_user_deactivated(self, user_id):
|
||||||
|
"""Called when a user ID is deactivated
|
||||||
|
"""
|
||||||
|
yield self.store.remove_from_user_dir(user_id)
|
||||||
|
yield self.store.remove_from_user_in_public_room(user_id)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _unsafe_process(self):
|
def _unsafe_process(self):
|
||||||
# If self.pos is None then means we haven't fetched it from DB
|
# If self.pos is None then means we haven't fetched it from DB
|
||||||
@@ -403,7 +411,7 @@ class UserDirectoryHandler(object):
|
|||||||
|
|
||||||
if change:
|
if change:
|
||||||
users_with_profile = yield self.state.get_current_user_in_room(room_id)
|
users_with_profile = yield self.state.get_current_user_in_room(room_id)
|
||||||
for user_id, profile in users_with_profile.iteritems():
|
for user_id, profile in iteritems(users_with_profile):
|
||||||
yield self._handle_new_user(room_id, user_id, profile)
|
yield self._handle_new_user(room_id, user_id, profile)
|
||||||
else:
|
else:
|
||||||
users = yield self.store.get_users_in_public_due_to_room(room_id)
|
users = yield self.store.get_users_in_public_due_to_room(room_id)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014-2016 OpenMarket Ltd
|
||||||
|
# Copyright 2018 New Vector Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -12,3 +13,24 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
from twisted.internet.defer import CancelledError
|
||||||
|
from twisted.python import failure
|
||||||
|
|
||||||
|
from synapse.api.errors import SynapseError
|
||||||
|
|
||||||
|
|
||||||
|
class RequestTimedOutError(SynapseError):
|
||||||
|
"""Exception representing timeout of an outbound request"""
|
||||||
|
def __init__(self):
|
||||||
|
super(RequestTimedOutError, self).__init__(504, "Timed out")
|
||||||
|
|
||||||
|
|
||||||
|
def cancelled_to_request_timed_out_error(value, timeout):
|
||||||
|
"""Turns CancelledErrors into RequestTimedOutErrors.
|
||||||
|
|
||||||
|
For use with async.add_timeout_to_deferred
|
||||||
|
"""
|
||||||
|
if isinstance(value, failure.Failure):
|
||||||
|
value.trap(CancelledError)
|
||||||
|
raise RequestTimedOutError()
|
||||||
|
return value
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from synapse.http.server import wrap_request_handler
|
from synapse.http.server import wrap_json_request_handler
|
||||||
from twisted.web.resource import Resource
|
from twisted.web.resource import Resource
|
||||||
from twisted.web.server import NOT_DONE_YET
|
from twisted.web.server import NOT_DONE_YET
|
||||||
|
|
||||||
@@ -42,14 +42,13 @@ class AdditionalResource(Resource):
|
|||||||
Resource.__init__(self)
|
Resource.__init__(self)
|
||||||
self._handler = handler
|
self._handler = handler
|
||||||
|
|
||||||
# these are required by the request_handler wrapper
|
# required by the request_handler wrapper
|
||||||
self.version_string = hs.version_string
|
|
||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
|
|
||||||
def render(self, request):
|
def render(self, request):
|
||||||
self._async_render(request)
|
self._async_render(request)
|
||||||
return NOT_DONE_YET
|
return NOT_DONE_YET
|
||||||
|
|
||||||
@wrap_request_handler
|
@wrap_json_request_handler
|
||||||
def _async_render(self, request):
|
def _async_render(self, request):
|
||||||
return self._handler(request)
|
return self._handler(request)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014-2016 OpenMarket Ltd
|
||||||
|
# Copyright 2018 New Vector Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -18,9 +19,10 @@ from OpenSSL.SSL import VERIFY_NONE
|
|||||||
from synapse.api.errors import (
|
from synapse.api.errors import (
|
||||||
CodeMessageException, MatrixCodeMessageException, SynapseError, Codes,
|
CodeMessageException, MatrixCodeMessageException, SynapseError, Codes,
|
||||||
)
|
)
|
||||||
|
from synapse.http import cancelled_to_request_timed_out_error
|
||||||
|
from synapse.util.async import add_timeout_to_deferred
|
||||||
from synapse.util.caches import CACHE_SIZE_FACTOR
|
from synapse.util.caches import CACHE_SIZE_FACTOR
|
||||||
from synapse.util.logcontext import make_deferred_yieldable
|
from synapse.util.logcontext import make_deferred_yieldable
|
||||||
from synapse.util import logcontext
|
|
||||||
import synapse.metrics
|
import synapse.metrics
|
||||||
from synapse.http.endpoint import SpiderEndpoint
|
from synapse.http.endpoint import SpiderEndpoint
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ from twisted.web.http import PotentialDataLoss
|
|||||||
from twisted.web.http_headers import Headers
|
from twisted.web.http_headers import Headers
|
||||||
from twisted.web._newclient import ResponseDone
|
from twisted.web._newclient import ResponseDone
|
||||||
|
|
||||||
from StringIO import StringIO
|
from six import StringIO
|
||||||
|
|
||||||
import simplejson as json
|
import simplejson as json
|
||||||
import logging
|
import logging
|
||||||
@@ -95,21 +97,17 @@ class SimpleHttpClient(object):
|
|||||||
# counters to it
|
# counters to it
|
||||||
outgoing_requests_counter.inc(method)
|
outgoing_requests_counter.inc(method)
|
||||||
|
|
||||||
def send_request():
|
|
||||||
request_deferred = self.agent.request(
|
|
||||||
method, uri, *args, **kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.clock.time_bound_deferred(
|
|
||||||
request_deferred,
|
|
||||||
time_out=60,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info("Sending request %s %s", method, uri)
|
logger.info("Sending request %s %s", method, uri)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with logcontext.PreserveLoggingContext():
|
request_deferred = self.agent.request(
|
||||||
response = yield send_request()
|
method, uri, *args, **kwargs
|
||||||
|
)
|
||||||
|
add_timeout_to_deferred(
|
||||||
|
request_deferred,
|
||||||
|
60, cancelled_to_request_timed_out_error,
|
||||||
|
)
|
||||||
|
response = yield make_deferred_yieldable(request_deferred)
|
||||||
|
|
||||||
incoming_responses_counter.inc(method, response.code)
|
incoming_responses_counter.inc(method, response.code)
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -509,7 +507,7 @@ class SpiderHttpClient(SimpleHttpClient):
|
|||||||
reactor,
|
reactor,
|
||||||
SpiderEndpointFactory(hs)
|
SpiderEndpointFactory(hs)
|
||||||
)
|
)
|
||||||
), [('gzip', GzipDecoder)]
|
), [(b'gzip', GzipDecoder)]
|
||||||
)
|
)
|
||||||
# We could look like Chrome:
|
# We could look like Chrome:
|
||||||
# self.user_agent = ("Mozilla/5.0 (%s) (KHTML, like Gecko)
|
# self.user_agent = ("Mozilla/5.0 (%s) (KHTML, like Gecko)
|
||||||
|
|||||||
@@ -115,10 +115,15 @@ class _WrappedConnection(object):
|
|||||||
if time.time() - self.last_request >= 2.5 * 60:
|
if time.time() - self.last_request >= 2.5 * 60:
|
||||||
self.abort()
|
self.abort()
|
||||||
# Abort the underlying TLS connection. The abort() method calls
|
# Abort the underlying TLS connection. The abort() method calls
|
||||||
# loseConnection() on the underlying TLS connection which tries to
|
# loseConnection() on the TLS connection which tries to
|
||||||
# shutdown the connection cleanly. We call abortConnection()
|
# shutdown the connection cleanly. We call abortConnection()
|
||||||
# since that will promptly close the underlying TCP connection.
|
# since that will promptly close the TLS connection.
|
||||||
self.transport.abortConnection()
|
#
|
||||||
|
# In Twisted >18.4; the TLS connection will be None if it has closed
|
||||||
|
# which will make abortConnection() throw. Check that the TLS connection
|
||||||
|
# is not None before trying to close it.
|
||||||
|
if self.transport.getHandle() is not None:
|
||||||
|
self.transport.abortConnection()
|
||||||
|
|
||||||
def request(self, request):
|
def request(self, request):
|
||||||
self.last_request = time.time()
|
self.last_request = time.time()
|
||||||
@@ -286,7 +291,7 @@ def resolve_service(service_name, dns_client=client, cache=SERVER_CACHE, clock=t
|
|||||||
if (len(answers) == 1
|
if (len(answers) == 1
|
||||||
and answers[0].type == dns.SRV
|
and answers[0].type == dns.SRV
|
||||||
and answers[0].payload
|
and answers[0].payload
|
||||||
and answers[0].payload.target == dns.Name('.')):
|
and answers[0].payload.target == dns.Name(b'.')):
|
||||||
raise ConnectError("Service %s unavailable" % service_name)
|
raise ConnectError("Service %s unavailable" % service_name)
|
||||||
|
|
||||||
for answer in answers:
|
for answer in answers:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014-2016 OpenMarket Ltd
|
||||||
|
# Copyright 2018 New Vector Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -12,17 +13,19 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import synapse.util.retryutils
|
|
||||||
from twisted.internet import defer, reactor, protocol
|
from twisted.internet import defer, reactor, protocol
|
||||||
from twisted.internet.error import DNSLookupError
|
from twisted.internet.error import DNSLookupError
|
||||||
from twisted.web.client import readBody, HTTPConnectionPool, Agent
|
from twisted.web.client import readBody, HTTPConnectionPool, Agent
|
||||||
from twisted.web.http_headers import Headers
|
from twisted.web.http_headers import Headers
|
||||||
from twisted.web._newclient import ResponseDone
|
from twisted.web._newclient import ResponseDone
|
||||||
|
|
||||||
|
from synapse.http import cancelled_to_request_timed_out_error
|
||||||
from synapse.http.endpoint import matrix_federation_endpoint
|
from synapse.http.endpoint import matrix_federation_endpoint
|
||||||
from synapse.util.async import sleep
|
|
||||||
from synapse.util import logcontext
|
|
||||||
import synapse.metrics
|
import synapse.metrics
|
||||||
|
from synapse.util.async import sleep, add_timeout_to_deferred
|
||||||
|
from synapse.util import logcontext
|
||||||
|
from synapse.util.logcontext import make_deferred_yieldable
|
||||||
|
import synapse.util.retryutils
|
||||||
|
|
||||||
from canonicaljson import encode_canonical_json
|
from canonicaljson import encode_canonical_json
|
||||||
|
|
||||||
@@ -38,7 +41,8 @@ import logging
|
|||||||
import random
|
import random
|
||||||
import sys
|
import sys
|
||||||
import urllib
|
import urllib
|
||||||
import urlparse
|
from six.moves.urllib import parse as urlparse
|
||||||
|
from six import string_types
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -184,21 +188,20 @@ class MatrixFederationHttpClient(object):
|
|||||||
producer = body_callback(method, http_url_bytes, headers_dict)
|
producer = body_callback(method, http_url_bytes, headers_dict)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
def send_request():
|
request_deferred = self.agent.request(
|
||||||
request_deferred = self.agent.request(
|
method,
|
||||||
method,
|
url_bytes,
|
||||||
url_bytes,
|
Headers(headers_dict),
|
||||||
Headers(headers_dict),
|
producer
|
||||||
producer
|
)
|
||||||
)
|
add_timeout_to_deferred(
|
||||||
|
request_deferred,
|
||||||
return self.clock.time_bound_deferred(
|
timeout / 1000. if timeout else 60,
|
||||||
request_deferred,
|
cancelled_to_request_timed_out_error,
|
||||||
time_out=timeout / 1000. if timeout else 60,
|
)
|
||||||
)
|
response = yield make_deferred_yieldable(
|
||||||
|
request_deferred,
|
||||||
with logcontext.PreserveLoggingContext():
|
)
|
||||||
response = yield send_request()
|
|
||||||
|
|
||||||
log_result = "%d %s" % (response.code, response.phrase,)
|
log_result = "%d %s" % (response.code, response.phrase,)
|
||||||
break
|
break
|
||||||
@@ -552,7 +555,7 @@ class MatrixFederationHttpClient(object):
|
|||||||
|
|
||||||
encoded_args = {}
|
encoded_args = {}
|
||||||
for k, vs in args.items():
|
for k, vs in args.items():
|
||||||
if isinstance(vs, basestring):
|
if isinstance(vs, string_types):
|
||||||
vs = [vs]
|
vs = [vs]
|
||||||
encoded_args[k] = [v.encode("UTF-8") for v in vs]
|
encoded_args[k] = [v.encode("UTF-8") for v in vs]
|
||||||
|
|
||||||
@@ -667,7 +670,7 @@ def check_content_type_is_json(headers):
|
|||||||
RuntimeError if the
|
RuntimeError if the
|
||||||
|
|
||||||
"""
|
"""
|
||||||
c_type = headers.getRawHeaders("Content-Type")
|
c_type = headers.getRawHeaders(b"Content-Type")
|
||||||
if c_type is None:
|
if c_type is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"No Content-Type header"
|
"No Content-Type header"
|
||||||
@@ -684,7 +687,7 @@ def check_content_type_is_json(headers):
|
|||||||
def encode_query_args(args):
|
def encode_query_args(args):
|
||||||
encoded_args = {}
|
encoded_args = {}
|
||||||
for k, vs in args.items():
|
for k, vs in args.items():
|
||||||
if isinstance(vs, basestring):
|
if isinstance(vs, string_types):
|
||||||
vs = [vs]
|
vs = [vs]
|
||||||
encoded_args[k] = [v.encode("UTF-8") for v in vs]
|
encoded_args[k] = [v.encode("UTF-8") for v in vs]
|
||||||
|
|
||||||
|
|||||||
307
synapse/http/request_metrics.py
Normal file
307
synapse/http/request_metrics.py
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2014-2016 OpenMarket Ltd
|
||||||
|
# Copyright 2018 New Vector Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import synapse.metrics
|
||||||
|
from synapse.util.logcontext import LoggingContext
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
metrics = synapse.metrics.get_metrics_for("synapse.http.server")
|
||||||
|
|
||||||
|
# total number of responses served, split by method/servlet/tag
|
||||||
|
response_count = metrics.register_counter(
|
||||||
|
"response_count",
|
||||||
|
labels=["method", "servlet", "tag"],
|
||||||
|
alternative_names=(
|
||||||
|
# the following are all deprecated aliases for the same metric
|
||||||
|
metrics.name_prefix + x for x in (
|
||||||
|
"_requests",
|
||||||
|
"_response_time:count",
|
||||||
|
"_response_ru_utime:count",
|
||||||
|
"_response_ru_stime:count",
|
||||||
|
"_response_db_txn_count:count",
|
||||||
|
"_response_db_txn_duration:count",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
requests_counter = metrics.register_counter(
|
||||||
|
"requests_received",
|
||||||
|
labels=["method", "servlet", ],
|
||||||
|
)
|
||||||
|
|
||||||
|
outgoing_responses_counter = metrics.register_counter(
|
||||||
|
"responses",
|
||||||
|
labels=["method", "code"],
|
||||||
|
)
|
||||||
|
|
||||||
|
response_timer = metrics.register_counter(
|
||||||
|
"response_time_seconds",
|
||||||
|
labels=["method", "servlet", "tag"],
|
||||||
|
alternative_names=(
|
||||||
|
metrics.name_prefix + "_response_time:total",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
response_ru_utime = metrics.register_counter(
|
||||||
|
"response_ru_utime_seconds", labels=["method", "servlet", "tag"],
|
||||||
|
alternative_names=(
|
||||||
|
metrics.name_prefix + "_response_ru_utime:total",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
response_ru_stime = metrics.register_counter(
|
||||||
|
"response_ru_stime_seconds", labels=["method", "servlet", "tag"],
|
||||||
|
alternative_names=(
|
||||||
|
metrics.name_prefix + "_response_ru_stime:total",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
response_db_txn_count = metrics.register_counter(
|
||||||
|
"response_db_txn_count", labels=["method", "servlet", "tag"],
|
||||||
|
alternative_names=(
|
||||||
|
metrics.name_prefix + "_response_db_txn_count:total",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# seconds spent waiting for db txns, excluding scheduling time, when processing
|
||||||
|
# this request
|
||||||
|
response_db_txn_duration = metrics.register_counter(
|
||||||
|
"response_db_txn_duration_seconds", labels=["method", "servlet", "tag"],
|
||||||
|
alternative_names=(
|
||||||
|
metrics.name_prefix + "_response_db_txn_duration:total",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# seconds spent waiting for a db connection, when processing this request
|
||||||
|
response_db_sched_duration = metrics.register_counter(
|
||||||
|
"response_db_sched_duration_seconds", labels=["method", "servlet", "tag"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# size in bytes of the response written
|
||||||
|
response_size = metrics.register_counter(
|
||||||
|
"response_size", labels=["method", "servlet", "tag"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# In flight metrics are incremented while the requests are in flight, rather
|
||||||
|
# than when the response was written.
|
||||||
|
|
||||||
|
in_flight_requests_ru_utime = metrics.register_counter(
|
||||||
|
"in_flight_requests_ru_utime_seconds", labels=["method", "servlet"],
|
||||||
|
)
|
||||||
|
|
||||||
|
in_flight_requests_ru_stime = metrics.register_counter(
|
||||||
|
"in_flight_requests_ru_stime_seconds", labels=["method", "servlet"],
|
||||||
|
)
|
||||||
|
|
||||||
|
in_flight_requests_db_txn_count = metrics.register_counter(
|
||||||
|
"in_flight_requests_db_txn_count", labels=["method", "servlet"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# seconds spent waiting for db txns, excluding scheduling time, when processing
|
||||||
|
# this request
|
||||||
|
in_flight_requests_db_txn_duration = metrics.register_counter(
|
||||||
|
"in_flight_requests_db_txn_duration_seconds", labels=["method", "servlet"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# seconds spent waiting for a db connection, when processing this request
|
||||||
|
in_flight_requests_db_sched_duration = metrics.register_counter(
|
||||||
|
"in_flight_requests_db_sched_duration_seconds", labels=["method", "servlet"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# The set of all in flight requests, set[RequestMetrics]
|
||||||
|
_in_flight_requests = set()
|
||||||
|
|
||||||
|
|
||||||
|
def _collect_in_flight():
|
||||||
|
"""Called just before metrics are collected, so we use it to update all
|
||||||
|
the in flight request metrics
|
||||||
|
"""
|
||||||
|
|
||||||
|
for rm in _in_flight_requests:
|
||||||
|
rm.update_metrics()
|
||||||
|
|
||||||
|
|
||||||
|
metrics.register_collector(_collect_in_flight)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_in_flight_counts():
|
||||||
|
"""Returns a count of all in flight requests by (method, server_name)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict[tuple[str, str], int]
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Map from (method, name) -> int, the number of in flight requests of that
|
||||||
|
# type
|
||||||
|
counts = {}
|
||||||
|
for rm in _in_flight_requests:
|
||||||
|
key = (rm.method, rm.name,)
|
||||||
|
counts[key] = counts.get(key, 0) + 1
|
||||||
|
|
||||||
|
return counts
|
||||||
|
|
||||||
|
|
||||||
|
metrics.register_callback(
|
||||||
|
"in_flight_requests_count",
|
||||||
|
_get_in_flight_counts,
|
||||||
|
labels=["method", "servlet"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RequestMetrics(object):
|
||||||
|
def start(self, time_msec, name, method):
|
||||||
|
self.start = time_msec
|
||||||
|
self.start_context = LoggingContext.current_context()
|
||||||
|
self.name = name
|
||||||
|
self.method = method
|
||||||
|
|
||||||
|
self._request_stats = _RequestStats.from_context(self.start_context)
|
||||||
|
|
||||||
|
_in_flight_requests.add(self)
|
||||||
|
|
||||||
|
def stop(self, time_msec, request):
|
||||||
|
_in_flight_requests.discard(self)
|
||||||
|
|
||||||
|
context = LoggingContext.current_context()
|
||||||
|
|
||||||
|
tag = ""
|
||||||
|
if context:
|
||||||
|
tag = context.tag
|
||||||
|
|
||||||
|
if context != self.start_context:
|
||||||
|
logger.warn(
|
||||||
|
"Context have unexpectedly changed %r, %r",
|
||||||
|
context, self.start_context
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
outgoing_responses_counter.inc(request.method, str(request.code))
|
||||||
|
|
||||||
|
response_count.inc(request.method, self.name, tag)
|
||||||
|
|
||||||
|
response_timer.inc_by(
|
||||||
|
time_msec - self.start, request.method,
|
||||||
|
self.name, tag
|
||||||
|
)
|
||||||
|
|
||||||
|
ru_utime, ru_stime = context.get_resource_usage()
|
||||||
|
|
||||||
|
response_ru_utime.inc_by(
|
||||||
|
ru_utime, request.method, self.name, tag
|
||||||
|
)
|
||||||
|
response_ru_stime.inc_by(
|
||||||
|
ru_stime, request.method, self.name, tag
|
||||||
|
)
|
||||||
|
response_db_txn_count.inc_by(
|
||||||
|
context.db_txn_count, request.method, self.name, tag
|
||||||
|
)
|
||||||
|
response_db_txn_duration.inc_by(
|
||||||
|
context.db_txn_duration_ms / 1000., request.method, self.name, tag
|
||||||
|
)
|
||||||
|
response_db_sched_duration.inc_by(
|
||||||
|
context.db_sched_duration_ms / 1000., request.method, self.name, tag
|
||||||
|
)
|
||||||
|
|
||||||
|
response_size.inc_by(request.sentLength, request.method, self.name, tag)
|
||||||
|
|
||||||
|
# We always call this at the end to ensure that we update the metrics
|
||||||
|
# regardless of whether a call to /metrics while the request was in
|
||||||
|
# flight.
|
||||||
|
self.update_metrics()
|
||||||
|
|
||||||
|
def update_metrics(self):
|
||||||
|
"""Updates the in flight metrics with values from this request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
diff = self._request_stats.update(self.start_context)
|
||||||
|
|
||||||
|
in_flight_requests_ru_utime.inc_by(
|
||||||
|
diff.ru_utime, self.method, self.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
in_flight_requests_ru_stime.inc_by(
|
||||||
|
diff.ru_stime, self.method, self.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
in_flight_requests_db_txn_count.inc_by(
|
||||||
|
diff.db_txn_count, self.method, self.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
in_flight_requests_db_txn_duration.inc_by(
|
||||||
|
diff.db_txn_duration_ms / 1000., self.method, self.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
in_flight_requests_db_sched_duration.inc_by(
|
||||||
|
diff.db_sched_duration_ms / 1000., self.method, self.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _RequestStats(object):
|
||||||
|
"""Keeps tracks of various metrics for an in flight request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = [
|
||||||
|
"ru_utime", "ru_stime",
|
||||||
|
"db_txn_count", "db_txn_duration_ms", "db_sched_duration_ms",
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, ru_utime, ru_stime, db_txn_count,
|
||||||
|
db_txn_duration_ms, db_sched_duration_ms):
|
||||||
|
self.ru_utime = ru_utime
|
||||||
|
self.ru_stime = ru_stime
|
||||||
|
self.db_txn_count = db_txn_count
|
||||||
|
self.db_txn_duration_ms = db_txn_duration_ms
|
||||||
|
self.db_sched_duration_ms = db_sched_duration_ms
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_context(context):
|
||||||
|
ru_utime, ru_stime = context.get_resource_usage()
|
||||||
|
|
||||||
|
return _RequestStats(
|
||||||
|
ru_utime, ru_stime,
|
||||||
|
context.db_txn_count,
|
||||||
|
context.db_txn_duration_ms,
|
||||||
|
context.db_sched_duration_ms,
|
||||||
|
)
|
||||||
|
|
||||||
|
def update(self, context):
|
||||||
|
"""Updates the current values and returns the difference between the
|
||||||
|
old and new values.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
_RequestStats: The difference between the old and new values
|
||||||
|
"""
|
||||||
|
new = _RequestStats.from_context(context)
|
||||||
|
|
||||||
|
diff = _RequestStats(
|
||||||
|
new.ru_utime - self.ru_utime,
|
||||||
|
new.ru_stime - self.ru_stime,
|
||||||
|
new.db_txn_count - self.db_txn_count,
|
||||||
|
new.db_txn_duration_ms - self.db_txn_duration_ms,
|
||||||
|
new.db_sched_duration_ms - self.db_sched_duration_ms,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.ru_utime = new.ru_utime
|
||||||
|
self.ru_stime = new.ru_stime
|
||||||
|
self.db_txn_count = new.db_txn_count
|
||||||
|
self.db_txn_duration_ms = new.db_txn_duration_ms
|
||||||
|
self.db_sched_duration_ms = new.db_sched_duration_ms
|
||||||
|
|
||||||
|
return diff
|
||||||
@@ -13,11 +13,15 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import cgi
|
||||||
|
from six.moves import http_client
|
||||||
|
|
||||||
from synapse.api.errors import (
|
from synapse.api.errors import (
|
||||||
cs_exception, SynapseError, CodeMessageException, UnrecognizedRequestError, Codes
|
cs_exception, SynapseError, CodeMessageException, UnrecognizedRequestError, Codes
|
||||||
)
|
)
|
||||||
|
from synapse.http.request_metrics import (
|
||||||
|
requests_counter,
|
||||||
|
)
|
||||||
from synapse.util.logcontext import LoggingContext, PreserveLoggingContext
|
from synapse.util.logcontext import LoggingContext, PreserveLoggingContext
|
||||||
from synapse.util.caches import intern_dict
|
from synapse.util.caches import intern_dict
|
||||||
from synapse.util.metrics import Measure
|
from synapse.util.metrics import Measure
|
||||||
@@ -41,178 +45,174 @@ import simplejson
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
metrics = synapse.metrics.get_metrics_for(__name__)
|
HTML_ERROR_TEMPLATE = """<!DOCTYPE html>
|
||||||
|
<html lang=en>
|
||||||
# total number of responses served, split by method/servlet/tag
|
<head>
|
||||||
response_count = metrics.register_counter(
|
<meta charset="utf-8">
|
||||||
"response_count",
|
<title>Error {code}</title>
|
||||||
labels=["method", "servlet", "tag"],
|
</head>
|
||||||
alternative_names=(
|
<body>
|
||||||
# the following are all deprecated aliases for the same metric
|
<p>{msg}</p>
|
||||||
metrics.name_prefix + x for x in (
|
</body>
|
||||||
"_requests",
|
</html>
|
||||||
"_response_time:count",
|
"""
|
||||||
"_response_ru_utime:count",
|
|
||||||
"_response_ru_stime:count",
|
|
||||||
"_response_db_txn_count:count",
|
|
||||||
"_response_db_txn_duration:count",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
requests_counter = metrics.register_counter(
|
|
||||||
"requests_received",
|
|
||||||
labels=["method", "servlet", ],
|
|
||||||
)
|
|
||||||
|
|
||||||
outgoing_responses_counter = metrics.register_counter(
|
|
||||||
"responses",
|
|
||||||
labels=["method", "code"],
|
|
||||||
)
|
|
||||||
|
|
||||||
response_timer = metrics.register_counter(
|
|
||||||
"response_time_seconds",
|
|
||||||
labels=["method", "servlet", "tag"],
|
|
||||||
alternative_names=(
|
|
||||||
metrics.name_prefix + "_response_time:total",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
response_ru_utime = metrics.register_counter(
|
|
||||||
"response_ru_utime_seconds", labels=["method", "servlet", "tag"],
|
|
||||||
alternative_names=(
|
|
||||||
metrics.name_prefix + "_response_ru_utime:total",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
response_ru_stime = metrics.register_counter(
|
|
||||||
"response_ru_stime_seconds", labels=["method", "servlet", "tag"],
|
|
||||||
alternative_names=(
|
|
||||||
metrics.name_prefix + "_response_ru_stime:total",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
response_db_txn_count = metrics.register_counter(
|
|
||||||
"response_db_txn_count", labels=["method", "servlet", "tag"],
|
|
||||||
alternative_names=(
|
|
||||||
metrics.name_prefix + "_response_db_txn_count:total",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# seconds spent waiting for db txns, excluding scheduling time, when processing
|
|
||||||
# this request
|
|
||||||
response_db_txn_duration = metrics.register_counter(
|
|
||||||
"response_db_txn_duration_seconds", labels=["method", "servlet", "tag"],
|
|
||||||
alternative_names=(
|
|
||||||
metrics.name_prefix + "_response_db_txn_duration:total",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# seconds spent waiting for a db connection, when processing this request
|
|
||||||
response_db_sched_duration = metrics.register_counter(
|
|
||||||
"response_db_sched_duration_seconds", labels=["method", "servlet", "tag"]
|
|
||||||
)
|
|
||||||
|
|
||||||
# size in bytes of the response written
|
|
||||||
response_size = metrics.register_counter(
|
|
||||||
"response_size", labels=["method", "servlet", "tag"]
|
|
||||||
)
|
|
||||||
|
|
||||||
_next_request_id = 0
|
|
||||||
|
|
||||||
|
|
||||||
def request_handler(include_metrics=False):
|
def wrap_json_request_handler(h):
|
||||||
"""Decorator for ``wrap_request_handler``"""
|
"""Wraps a request handler method with exception handling.
|
||||||
return lambda request_handler: wrap_request_handler(request_handler, include_metrics)
|
|
||||||
|
|
||||||
|
Also adds logging as per wrap_request_handler_with_logging.
|
||||||
|
|
||||||
def wrap_request_handler(request_handler, include_metrics=False):
|
The handler method must have a signature of "handle_foo(self, request)",
|
||||||
"""Wraps a method that acts as a request handler with the necessary logging
|
where "self" must have a "clock" attribute (and "request" must be a
|
||||||
and exception handling.
|
SynapseRequest).
|
||||||
|
|
||||||
The method must have a signature of "handle_foo(self, request)". The
|
The handler must return a deferred. If the deferred succeeds we assume that
|
||||||
argument "self" must have "version_string" and "clock" attributes. The
|
|
||||||
argument "request" must be a twisted HTTP request.
|
|
||||||
|
|
||||||
The method must return a deferred. If the deferred succeeds we assume that
|
|
||||||
a response has been sent. If the deferred fails with a SynapseError we use
|
a response has been sent. If the deferred fails with a SynapseError we use
|
||||||
it to send a JSON response with the appropriate HTTP reponse code. If the
|
it to send a JSON response with the appropriate HTTP reponse code. If the
|
||||||
deferred fails with any other type of error we send a 500 reponse.
|
deferred fails with any other type of error we send a 500 reponse.
|
||||||
|
|
||||||
We insert a unique request-id into the logging context for this request and
|
|
||||||
log the response and duration for this request.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def wrapped_request_handler(self, request):
|
def wrapped_request_handler(self, request):
|
||||||
global _next_request_id
|
try:
|
||||||
request_id = "%s-%s" % (request.method, _next_request_id)
|
yield h(self, request)
|
||||||
_next_request_id += 1
|
except CodeMessageException as e:
|
||||||
|
code = e.code
|
||||||
|
if isinstance(e, SynapseError):
|
||||||
|
logger.info(
|
||||||
|
"%s SynapseError: %s - %s", request, code, e.msg
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.exception(e)
|
||||||
|
respond_with_json(
|
||||||
|
request, code, cs_exception(e), send_cors=True,
|
||||||
|
pretty_print=_request_user_agent_is_curl(request),
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
# failure.Failure() fishes the original Failure out
|
||||||
|
# of our stack, and thus gives us a sensible stack
|
||||||
|
# trace.
|
||||||
|
f = failure.Failure()
|
||||||
|
logger.error(
|
||||||
|
"Failed handle request via %r: %r: %s",
|
||||||
|
h,
|
||||||
|
request,
|
||||||
|
f.getTraceback().rstrip(),
|
||||||
|
)
|
||||||
|
respond_with_json(
|
||||||
|
request,
|
||||||
|
500,
|
||||||
|
{
|
||||||
|
"error": "Internal server error",
|
||||||
|
"errcode": Codes.UNKNOWN,
|
||||||
|
},
|
||||||
|
send_cors=True,
|
||||||
|
pretty_print=_request_user_agent_is_curl(request),
|
||||||
|
)
|
||||||
|
|
||||||
|
return wrap_request_handler_with_logging(wrapped_request_handler)
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_html_request_handler(h):
|
||||||
|
"""Wraps a request handler method with exception handling.
|
||||||
|
|
||||||
|
Also adds logging as per wrap_request_handler_with_logging.
|
||||||
|
|
||||||
|
The handler method must have a signature of "handle_foo(self, request)",
|
||||||
|
where "self" must have a "clock" attribute (and "request" must be a
|
||||||
|
SynapseRequest).
|
||||||
|
"""
|
||||||
|
def wrapped_request_handler(self, request):
|
||||||
|
d = defer.maybeDeferred(h, self, request)
|
||||||
|
d.addErrback(_return_html_error, request)
|
||||||
|
return d
|
||||||
|
|
||||||
|
return wrap_request_handler_with_logging(wrapped_request_handler)
|
||||||
|
|
||||||
|
|
||||||
|
def _return_html_error(f, request):
|
||||||
|
"""Sends an HTML error page corresponding to the given failure
|
||||||
|
|
||||||
|
Args:
|
||||||
|
f (twisted.python.failure.Failure):
|
||||||
|
request (twisted.web.iweb.IRequest):
|
||||||
|
"""
|
||||||
|
if f.check(CodeMessageException):
|
||||||
|
cme = f.value
|
||||||
|
code = cme.code
|
||||||
|
msg = cme.msg
|
||||||
|
|
||||||
|
if isinstance(cme, SynapseError):
|
||||||
|
logger.info(
|
||||||
|
"%s SynapseError: %s - %s", request, code, msg
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.error(
|
||||||
|
"Failed handle request %r: %s",
|
||||||
|
request,
|
||||||
|
f.getTraceback().rstrip(),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
code = http_client.INTERNAL_SERVER_ERROR
|
||||||
|
msg = "Internal server error"
|
||||||
|
|
||||||
|
logger.error(
|
||||||
|
"Failed handle request %r: %s",
|
||||||
|
request,
|
||||||
|
f.getTraceback().rstrip(),
|
||||||
|
)
|
||||||
|
|
||||||
|
body = HTML_ERROR_TEMPLATE.format(
|
||||||
|
code=code, msg=cgi.escape(msg),
|
||||||
|
).encode("utf-8")
|
||||||
|
request.setResponseCode(code)
|
||||||
|
request.setHeader(b"Content-Type", b"text/html; charset=utf-8")
|
||||||
|
request.setHeader(b"Content-Length", b"%i" % (len(body),))
|
||||||
|
request.write(body)
|
||||||
|
finish_request(request)
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_request_handler_with_logging(h):
|
||||||
|
"""Wraps a request handler to provide logging and metrics
|
||||||
|
|
||||||
|
The handler method must have a signature of "handle_foo(self, request)",
|
||||||
|
where "self" must have a "clock" attribute (and "request" must be a
|
||||||
|
SynapseRequest).
|
||||||
|
|
||||||
|
As well as calling `request.processing` (which will log the response and
|
||||||
|
duration for this request), the wrapped request handler will insert the
|
||||||
|
request id into the logging context.
|
||||||
|
"""
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def wrapped_request_handler(self, request):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
self:
|
||||||
|
request (synapse.http.site.SynapseRequest):
|
||||||
|
"""
|
||||||
|
|
||||||
|
request_id = request.get_request_id()
|
||||||
with LoggingContext(request_id) as request_context:
|
with LoggingContext(request_id) as request_context:
|
||||||
|
request_context.request = request_id
|
||||||
with Measure(self.clock, "wrapped_request_handler"):
|
with Measure(self.clock, "wrapped_request_handler"):
|
||||||
request_metrics = RequestMetrics()
|
|
||||||
# we start the request metrics timer here with an initial stab
|
# we start the request metrics timer here with an initial stab
|
||||||
# at the servlet name. For most requests that name will be
|
# at the servlet name. For most requests that name will be
|
||||||
# JsonResource (or a subclass), and JsonResource._async_render
|
# JsonResource (or a subclass), and JsonResource._async_render
|
||||||
# will update it once it picks a servlet.
|
# will update it once it picks a servlet.
|
||||||
servlet_name = self.__class__.__name__
|
servlet_name = self.__class__.__name__
|
||||||
request_metrics.start(self.clock, name=servlet_name)
|
with request.processing(servlet_name):
|
||||||
|
with PreserveLoggingContext(request_context):
|
||||||
|
d = defer.maybeDeferred(h, self, request)
|
||||||
|
|
||||||
request_context.request = request_id
|
# record the arrival of the request *after*
|
||||||
with request.processing():
|
# dispatching to the handler, so that the handler
|
||||||
try:
|
# can update the servlet name in the request
|
||||||
with PreserveLoggingContext(request_context):
|
# metrics
|
||||||
if include_metrics:
|
requests_counter.inc(request.method,
|
||||||
yield request_handler(self, request, request_metrics)
|
request.request_metrics.name)
|
||||||
else:
|
yield d
|
||||||
requests_counter.inc(request.method, servlet_name)
|
|
||||||
yield request_handler(self, request)
|
|
||||||
except CodeMessageException as e:
|
|
||||||
code = e.code
|
|
||||||
if isinstance(e, SynapseError):
|
|
||||||
logger.info(
|
|
||||||
"%s SynapseError: %s - %s", request, code, e.msg
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.exception(e)
|
|
||||||
outgoing_responses_counter.inc(request.method, str(code))
|
|
||||||
respond_with_json(
|
|
||||||
request, code, cs_exception(e), send_cors=True,
|
|
||||||
pretty_print=_request_user_agent_is_curl(request),
|
|
||||||
version_string=self.version_string,
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
# failure.Failure() fishes the original Failure out
|
|
||||||
# of our stack, and thus gives us a sensible stack
|
|
||||||
# trace.
|
|
||||||
f = failure.Failure()
|
|
||||||
logger.error(
|
|
||||||
"Failed handle request %s.%s on %r: %r: %s",
|
|
||||||
request_handler.__module__,
|
|
||||||
request_handler.__name__,
|
|
||||||
self,
|
|
||||||
request,
|
|
||||||
f.getTraceback().rstrip(),
|
|
||||||
)
|
|
||||||
respond_with_json(
|
|
||||||
request,
|
|
||||||
500,
|
|
||||||
{
|
|
||||||
"error": "Internal server error",
|
|
||||||
"errcode": Codes.UNKNOWN,
|
|
||||||
},
|
|
||||||
send_cors=True,
|
|
||||||
pretty_print=_request_user_agent_is_curl(request),
|
|
||||||
version_string=self.version_string,
|
|
||||||
)
|
|
||||||
finally:
|
|
||||||
try:
|
|
||||||
request_metrics.stop(
|
|
||||||
self.clock, request
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warn("Failed to stop metrics: %r", e)
|
|
||||||
return wrapped_request_handler
|
return wrapped_request_handler
|
||||||
|
|
||||||
|
|
||||||
@@ -262,7 +262,6 @@ class JsonResource(HttpServer, resource.Resource):
|
|||||||
self.canonical_json = canonical_json
|
self.canonical_json = canonical_json
|
||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
self.path_regexs = {}
|
self.path_regexs = {}
|
||||||
self.version_string = hs.version_string
|
|
||||||
self.hs = hs
|
self.hs = hs
|
||||||
|
|
||||||
def register_paths(self, method, path_patterns, callback):
|
def register_paths(self, method, path_patterns, callback):
|
||||||
@@ -278,13 +277,9 @@ class JsonResource(HttpServer, resource.Resource):
|
|||||||
self._async_render(request)
|
self._async_render(request)
|
||||||
return server.NOT_DONE_YET
|
return server.NOT_DONE_YET
|
||||||
|
|
||||||
# Disable metric reporting because _async_render does its own metrics.
|
@wrap_json_request_handler
|
||||||
# It does its own metric reporting because _async_render dispatches to
|
|
||||||
# a callback and it's the class name of that callback we want to report
|
|
||||||
# against rather than the JsonResource itself.
|
|
||||||
@request_handler(include_metrics=True)
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _async_render(self, request, request_metrics):
|
def _async_render(self, request):
|
||||||
""" This gets called from render() every time someone sends us a request.
|
""" This gets called from render() every time someone sends us a request.
|
||||||
This checks if anyone has registered a callback for that method and
|
This checks if anyone has registered a callback for that method and
|
||||||
path.
|
path.
|
||||||
@@ -296,9 +291,7 @@ class JsonResource(HttpServer, resource.Resource):
|
|||||||
servlet_classname = servlet_instance.__class__.__name__
|
servlet_classname = servlet_instance.__class__.__name__
|
||||||
else:
|
else:
|
||||||
servlet_classname = "%r" % callback
|
servlet_classname = "%r" % callback
|
||||||
|
request.request_metrics.name = servlet_classname
|
||||||
request_metrics.name = servlet_classname
|
|
||||||
requests_counter.inc(request.method, servlet_classname)
|
|
||||||
|
|
||||||
# Now trigger the callback. If it returns a response, we send it
|
# Now trigger the callback. If it returns a response, we send it
|
||||||
# here. If it throws an exception, that is handled by the wrapper
|
# here. If it throws an exception, that is handled by the wrapper
|
||||||
@@ -345,15 +338,12 @@ class JsonResource(HttpServer, resource.Resource):
|
|||||||
|
|
||||||
def _send_response(self, request, code, response_json_object,
|
def _send_response(self, request, code, response_json_object,
|
||||||
response_code_message=None):
|
response_code_message=None):
|
||||||
outgoing_responses_counter.inc(request.method, str(code))
|
|
||||||
|
|
||||||
# TODO: Only enable CORS for the requests that need it.
|
# TODO: Only enable CORS for the requests that need it.
|
||||||
respond_with_json(
|
respond_with_json(
|
||||||
request, code, response_json_object,
|
request, code, response_json_object,
|
||||||
send_cors=True,
|
send_cors=True,
|
||||||
response_code_message=response_code_message,
|
response_code_message=response_code_message,
|
||||||
pretty_print=_request_user_agent_is_curl(request),
|
pretty_print=_request_user_agent_is_curl(request),
|
||||||
version_string=self.version_string,
|
|
||||||
canonical_json=self.canonical_json,
|
canonical_json=self.canonical_json,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -386,54 +376,6 @@ def _unrecognised_request_handler(request):
|
|||||||
raise UnrecognizedRequestError()
|
raise UnrecognizedRequestError()
|
||||||
|
|
||||||
|
|
||||||
class RequestMetrics(object):
|
|
||||||
def start(self, clock, name):
|
|
||||||
self.start = clock.time_msec()
|
|
||||||
self.start_context = LoggingContext.current_context()
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
def stop(self, clock, request):
|
|
||||||
context = LoggingContext.current_context()
|
|
||||||
|
|
||||||
tag = ""
|
|
||||||
if context:
|
|
||||||
tag = context.tag
|
|
||||||
|
|
||||||
if context != self.start_context:
|
|
||||||
logger.warn(
|
|
||||||
"Context have unexpectedly changed %r, %r",
|
|
||||||
context, self.start_context
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
response_count.inc(request.method, self.name, tag)
|
|
||||||
|
|
||||||
response_timer.inc_by(
|
|
||||||
clock.time_msec() - self.start, request.method,
|
|
||||||
self.name, tag
|
|
||||||
)
|
|
||||||
|
|
||||||
ru_utime, ru_stime = context.get_resource_usage()
|
|
||||||
|
|
||||||
response_ru_utime.inc_by(
|
|
||||||
ru_utime, request.method, self.name, tag
|
|
||||||
)
|
|
||||||
response_ru_stime.inc_by(
|
|
||||||
ru_stime, request.method, self.name, tag
|
|
||||||
)
|
|
||||||
response_db_txn_count.inc_by(
|
|
||||||
context.db_txn_count, request.method, self.name, tag
|
|
||||||
)
|
|
||||||
response_db_txn_duration.inc_by(
|
|
||||||
context.db_txn_duration_ms / 1000., request.method, self.name, tag
|
|
||||||
)
|
|
||||||
response_db_sched_duration.inc_by(
|
|
||||||
context.db_sched_duration_ms / 1000., request.method, self.name, tag
|
|
||||||
)
|
|
||||||
|
|
||||||
response_size.inc_by(request.sentLength, request.method, self.name, tag)
|
|
||||||
|
|
||||||
|
|
||||||
class RootRedirect(resource.Resource):
|
class RootRedirect(resource.Resource):
|
||||||
"""Redirects the root '/' path to another path."""
|
"""Redirects the root '/' path to another path."""
|
||||||
|
|
||||||
@@ -452,7 +394,7 @@ class RootRedirect(resource.Resource):
|
|||||||
|
|
||||||
def respond_with_json(request, code, json_object, send_cors=False,
|
def respond_with_json(request, code, json_object, send_cors=False,
|
||||||
response_code_message=None, pretty_print=False,
|
response_code_message=None, pretty_print=False,
|
||||||
version_string="", canonical_json=True):
|
canonical_json=True):
|
||||||
# could alternatively use request.notifyFinish() and flip a flag when
|
# could alternatively use request.notifyFinish() and flip a flag when
|
||||||
# the Deferred fires, but since the flag is RIGHT THERE it seems like
|
# the Deferred fires, but since the flag is RIGHT THERE it seems like
|
||||||
# a waste.
|
# a waste.
|
||||||
@@ -474,12 +416,11 @@ def respond_with_json(request, code, json_object, send_cors=False,
|
|||||||
request, code, json_bytes,
|
request, code, json_bytes,
|
||||||
send_cors=send_cors,
|
send_cors=send_cors,
|
||||||
response_code_message=response_code_message,
|
response_code_message=response_code_message,
|
||||||
version_string=version_string
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def respond_with_json_bytes(request, code, json_bytes, send_cors=False,
|
def respond_with_json_bytes(request, code, json_bytes, send_cors=False,
|
||||||
version_string="", response_code_message=None):
|
response_code_message=None):
|
||||||
"""Sends encoded JSON in response to the given request.
|
"""Sends encoded JSON in response to the given request.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -493,7 +434,6 @@ def respond_with_json_bytes(request, code, json_bytes, send_cors=False,
|
|||||||
|
|
||||||
request.setResponseCode(code, message=response_code_message)
|
request.setResponseCode(code, message=response_code_message)
|
||||||
request.setHeader(b"Content-Type", b"application/json")
|
request.setHeader(b"Content-Type", b"application/json")
|
||||||
request.setHeader(b"Server", version_string)
|
|
||||||
request.setHeader(b"Content-Length", b"%d" % (len(json_bytes),))
|
request.setHeader(b"Content-Length", b"%d" % (len(json_bytes),))
|
||||||
request.setHeader(b"Cache-Control", b"no-cache, no-store, must-revalidate")
|
request.setHeader(b"Cache-Control", b"no-cache, no-store, must-revalidate")
|
||||||
|
|
||||||
@@ -546,6 +486,6 @@ def _request_user_agent_is_curl(request):
|
|||||||
b"User-Agent", default=[]
|
b"User-Agent", default=[]
|
||||||
)
|
)
|
||||||
for user_agent in user_agents:
|
for user_agent in user_agents:
|
||||||
if "curl" in user_agent:
|
if b"curl" in user_agent:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -12,27 +12,51 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from synapse.util.logcontext import LoggingContext
|
|
||||||
from twisted.web.server import Site, Request
|
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from twisted.web.server import Site, Request
|
||||||
|
|
||||||
|
from synapse.http.request_metrics import RequestMetrics
|
||||||
|
from synapse.util.logcontext import LoggingContext
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
ACCESS_TOKEN_RE = re.compile(br'(\?.*access(_|%5[Ff])token=)[^&]*(.*)$')
|
ACCESS_TOKEN_RE = re.compile(br'(\?.*access(_|%5[Ff])token=)[^&]*(.*)$')
|
||||||
|
|
||||||
|
_next_request_seq = 0
|
||||||
|
|
||||||
|
|
||||||
class SynapseRequest(Request):
|
class SynapseRequest(Request):
|
||||||
|
"""Class which encapsulates an HTTP request to synapse.
|
||||||
|
|
||||||
|
All of the requests processed in synapse are of this type.
|
||||||
|
|
||||||
|
It extends twisted's twisted.web.server.Request, and adds:
|
||||||
|
* Unique request ID
|
||||||
|
* Redaction of access_token query-params in __repr__
|
||||||
|
* Logging at start and end
|
||||||
|
* Metrics to record CPU, wallclock and DB time by endpoint.
|
||||||
|
|
||||||
|
It provides a method `processing` which should be called by the Resource
|
||||||
|
which is handling the request, and returns a context manager.
|
||||||
|
|
||||||
|
"""
|
||||||
def __init__(self, site, *args, **kw):
|
def __init__(self, site, *args, **kw):
|
||||||
Request.__init__(self, *args, **kw)
|
Request.__init__(self, *args, **kw)
|
||||||
self.site = site
|
self.site = site
|
||||||
self.authenticated_entity = None
|
self.authenticated_entity = None
|
||||||
self.start_time = 0
|
self.start_time = 0
|
||||||
|
|
||||||
|
global _next_request_seq
|
||||||
|
self.request_seq = _next_request_seq
|
||||||
|
_next_request_seq += 1
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
# We overwrite this so that we don't log ``access_token``
|
# We overwrite this so that we don't log ``access_token``
|
||||||
return '<%s at 0x%x method=%s uri=%s clientproto=%s site=%s>' % (
|
return '<%s at 0x%x method=%r uri=%r clientproto=%r site=%r>' % (
|
||||||
self.__class__.__name__,
|
self.__class__.__name__,
|
||||||
id(self),
|
id(self),
|
||||||
self.method,
|
self.method,
|
||||||
@@ -41,6 +65,9 @@ class SynapseRequest(Request):
|
|||||||
self.site.site_tag,
|
self.site.site_tag,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_request_id(self):
|
||||||
|
return "%s-%i" % (self.method, self.request_seq)
|
||||||
|
|
||||||
def get_redacted_uri(self):
|
def get_redacted_uri(self):
|
||||||
return ACCESS_TOKEN_RE.sub(
|
return ACCESS_TOKEN_RE.sub(
|
||||||
br'\1<redacted>\3',
|
br'\1<redacted>\3',
|
||||||
@@ -50,7 +77,18 @@ class SynapseRequest(Request):
|
|||||||
def get_user_agent(self):
|
def get_user_agent(self):
|
||||||
return self.requestHeaders.getRawHeaders(b"User-Agent", [None])[-1]
|
return self.requestHeaders.getRawHeaders(b"User-Agent", [None])[-1]
|
||||||
|
|
||||||
def started_processing(self):
|
def render(self, resrc):
|
||||||
|
# override the Server header which is set by twisted
|
||||||
|
self.setHeader("Server", self.site.server_version_string)
|
||||||
|
return Request.render(self, resrc)
|
||||||
|
|
||||||
|
def _started_processing(self, servlet_name):
|
||||||
|
self.start_time = int(time.time() * 1000)
|
||||||
|
self.request_metrics = RequestMetrics()
|
||||||
|
self.request_metrics.start(
|
||||||
|
self.start_time, name=servlet_name, method=self.method,
|
||||||
|
)
|
||||||
|
|
||||||
self.site.access_logger.info(
|
self.site.access_logger.info(
|
||||||
"%s - %s - Received request: %s %s",
|
"%s - %s - Received request: %s %s",
|
||||||
self.getClientIP(),
|
self.getClientIP(),
|
||||||
@@ -58,10 +96,8 @@ class SynapseRequest(Request):
|
|||||||
self.method,
|
self.method,
|
||||||
self.get_redacted_uri()
|
self.get_redacted_uri()
|
||||||
)
|
)
|
||||||
self.start_time = int(time.time() * 1000)
|
|
||||||
|
|
||||||
def finished_processing(self):
|
|
||||||
|
|
||||||
|
def _finished_processing(self):
|
||||||
try:
|
try:
|
||||||
context = LoggingContext.current_context()
|
context = LoggingContext.current_context()
|
||||||
ru_utime, ru_stime = context.get_resource_usage()
|
ru_utime, ru_stime = context.get_resource_usage()
|
||||||
@@ -72,6 +108,8 @@ class SynapseRequest(Request):
|
|||||||
ru_utime, ru_stime = (0, 0)
|
ru_utime, ru_stime = (0, 0)
|
||||||
db_txn_count, db_txn_duration_ms = (0, 0)
|
db_txn_count, db_txn_duration_ms = (0, 0)
|
||||||
|
|
||||||
|
end_time = int(time.time() * 1000)
|
||||||
|
|
||||||
self.site.access_logger.info(
|
self.site.access_logger.info(
|
||||||
"%s - %s - {%s}"
|
"%s - %s - {%s}"
|
||||||
" Processed request: %dms (%dms, %dms) (%dms/%dms/%d)"
|
" Processed request: %dms (%dms, %dms) (%dms/%dms/%d)"
|
||||||
@@ -79,7 +117,7 @@ class SynapseRequest(Request):
|
|||||||
self.getClientIP(),
|
self.getClientIP(),
|
||||||
self.site.site_tag,
|
self.site.site_tag,
|
||||||
self.authenticated_entity,
|
self.authenticated_entity,
|
||||||
int(time.time() * 1000) - self.start_time,
|
end_time - self.start_time,
|
||||||
int(ru_utime * 1000),
|
int(ru_utime * 1000),
|
||||||
int(ru_stime * 1000),
|
int(ru_stime * 1000),
|
||||||
db_sched_duration_ms,
|
db_sched_duration_ms,
|
||||||
@@ -93,11 +131,38 @@ class SynapseRequest(Request):
|
|||||||
self.get_user_agent(),
|
self.get_user_agent(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.request_metrics.stop(end_time, self)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warn("Failed to stop metrics: %r", e)
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def processing(self):
|
def processing(self, servlet_name):
|
||||||
self.started_processing()
|
"""Record the fact that we are processing this request.
|
||||||
|
|
||||||
|
Returns a context manager; the correct way to use this is:
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def handle_request(request):
|
||||||
|
with request.processing("FooServlet"):
|
||||||
|
yield really_handle_the_request()
|
||||||
|
|
||||||
|
This will log the request's arrival. Once the context manager is
|
||||||
|
closed, the completion of the request will be logged, and the various
|
||||||
|
metrics will be updated.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
servlet_name (str): the name of the servlet which will be
|
||||||
|
processing this request. This is used in the metrics.
|
||||||
|
|
||||||
|
It is possible to update this afterwards by updating
|
||||||
|
self.request_metrics.servlet_name.
|
||||||
|
"""
|
||||||
|
# TODO: we should probably just move this into render() and finish(),
|
||||||
|
# to save having to call a separate method.
|
||||||
|
self._started_processing(servlet_name)
|
||||||
yield
|
yield
|
||||||
self.finished_processing()
|
self._finished_processing()
|
||||||
|
|
||||||
|
|
||||||
class XForwardedForRequest(SynapseRequest):
|
class XForwardedForRequest(SynapseRequest):
|
||||||
@@ -135,7 +200,8 @@ class SynapseSite(Site):
|
|||||||
Subclass of a twisted http Site that does access logging with python's
|
Subclass of a twisted http Site that does access logging with python's
|
||||||
standard logging
|
standard logging
|
||||||
"""
|
"""
|
||||||
def __init__(self, logger_name, site_tag, config, resource, *args, **kwargs):
|
def __init__(self, logger_name, site_tag, config, resource,
|
||||||
|
server_version_string, *args, **kwargs):
|
||||||
Site.__init__(self, resource, *args, **kwargs)
|
Site.__init__(self, resource, *args, **kwargs)
|
||||||
|
|
||||||
self.site_tag = site_tag
|
self.site_tag = site_tag
|
||||||
@@ -143,6 +209,7 @@ class SynapseSite(Site):
|
|||||||
proxied = config.get("x_forwarded", False)
|
proxied = config.get("x_forwarded", False)
|
||||||
self.requestFactory = SynapseRequestFactory(self, proxied)
|
self.requestFactory = SynapseRequestFactory(self, proxied)
|
||||||
self.access_logger = logging.getLogger(logger_name)
|
self.access_logger = logging.getLogger(logger_name)
|
||||||
|
self.server_version_string = server_version_string
|
||||||
|
|
||||||
def log(self, request):
|
def log(self, request):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -56,8 +57,7 @@ class BaseMetric(object):
|
|||||||
return not len(self.labels)
|
return not len(self.labels)
|
||||||
|
|
||||||
def _render_labelvalue(self, value):
|
def _render_labelvalue(self, value):
|
||||||
# TODO: escape backslashes, quotes and newlines
|
return '"%s"' % (_escape_label_value(value),)
|
||||||
return '"%s"' % (value)
|
|
||||||
|
|
||||||
def _render_key(self, values):
|
def _render_key(self, values):
|
||||||
if self.is_scalar():
|
if self.is_scalar():
|
||||||
@@ -71,7 +71,8 @@ class BaseMetric(object):
|
|||||||
"""Render this metric for a single set of labels
|
"""Render this metric for a single set of labels
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
label_values (list[str]): values for each of the labels
|
label_values (list[object]): values for each of the labels,
|
||||||
|
(which get stringified).
|
||||||
value: value of the metric at with these labels
|
value: value of the metric at with these labels
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -299,3 +300,29 @@ class MemoryUsageMetric(object):
|
|||||||
"process_psutil_rss:total %d" % sum_rss,
|
"process_psutil_rss:total %d" % sum_rss,
|
||||||
"process_psutil_rss:count %d" % len_rss,
|
"process_psutil_rss:count %d" % len_rss,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _escape_character(m):
|
||||||
|
"""Replaces a single character with its escape sequence.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
m (re.MatchObject): A match object whose first group is the single
|
||||||
|
character to replace
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str
|
||||||
|
"""
|
||||||
|
c = m.group(1)
|
||||||
|
if c == "\\":
|
||||||
|
return "\\\\"
|
||||||
|
elif c == "\"":
|
||||||
|
return "\\\""
|
||||||
|
elif c == "\n":
|
||||||
|
return "\\n"
|
||||||
|
return c
|
||||||
|
|
||||||
|
|
||||||
|
def _escape_label_value(value):
|
||||||
|
"""Takes a label value and escapes quotes, newlines and backslashes
|
||||||
|
"""
|
||||||
|
return re.sub(r"([\n\"\\])", _escape_character, str(value))
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
TICKS_PER_SEC = 100
|
TICKS_PER_SEC = 100
|
||||||
BYTES_PER_PAGE = 4096
|
BYTES_PER_PAGE = 4096
|
||||||
@@ -55,7 +56,7 @@ def update_resource_metrics():
|
|||||||
# line is PID (command) more stats go here ...
|
# line is PID (command) more stats go here ...
|
||||||
raw_stats = line.split(") ", 1)[1].split(" ")
|
raw_stats = line.split(") ", 1)[1].split(" ")
|
||||||
|
|
||||||
for (name, index) in STAT_FIELDS.iteritems():
|
for (name, index) in iteritems(STAT_FIELDS):
|
||||||
# subtract 3 from the index, because proc(5) is 1-based, and
|
# subtract 3 from the index, because proc(5) is 1-based, and
|
||||||
# we've lost the first two fields in PID and COMMAND above
|
# we've lost the first two fields in PID and COMMAND above
|
||||||
stats[name] = int(raw_stats[index - 3])
|
stats[name] = int(raw_stats[index - 3])
|
||||||
|
|||||||
@@ -14,14 +14,17 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.api.constants import EventTypes, Membership
|
from synapse.api.constants import EventTypes, Membership
|
||||||
from synapse.api.errors import AuthError
|
from synapse.api.errors import AuthError
|
||||||
from synapse.handlers.presence import format_user_presence_state
|
from synapse.handlers.presence import format_user_presence_state
|
||||||
|
|
||||||
from synapse.util import DeferredTimedOutError
|
|
||||||
from synapse.util.logutils import log_function
|
from synapse.util.logutils import log_function
|
||||||
from synapse.util.async import ObservableDeferred
|
from synapse.util.async import (
|
||||||
from synapse.util.logcontext import PreserveLoggingContext, preserve_fn
|
ObservableDeferred, add_timeout_to_deferred,
|
||||||
|
DeferredTimeoutError,
|
||||||
|
)
|
||||||
|
from synapse.util.logcontext import PreserveLoggingContext, run_in_background
|
||||||
from synapse.util.metrics import Measure
|
from synapse.util.metrics import Measure
|
||||||
from synapse.types import StreamToken
|
from synapse.types import StreamToken
|
||||||
from synapse.visibility import filter_events_for_client
|
from synapse.visibility import filter_events_for_client
|
||||||
@@ -144,6 +147,7 @@ class _NotifierUserStream(object):
|
|||||||
class EventStreamResult(namedtuple("EventStreamResult", ("events", "tokens"))):
|
class EventStreamResult(namedtuple("EventStreamResult", ("events", "tokens"))):
|
||||||
def __nonzero__(self):
|
def __nonzero__(self):
|
||||||
return bool(self.events)
|
return bool(self.events)
|
||||||
|
__bool__ = __nonzero__ # python3
|
||||||
|
|
||||||
|
|
||||||
class Notifier(object):
|
class Notifier(object):
|
||||||
@@ -250,9 +254,7 @@ class Notifier(object):
|
|||||||
def _on_new_room_event(self, event, room_stream_id, extra_users=[]):
|
def _on_new_room_event(self, event, room_stream_id, extra_users=[]):
|
||||||
"""Notify any user streams that are interested in this room event"""
|
"""Notify any user streams that are interested in this room event"""
|
||||||
# poke any interested application service.
|
# poke any interested application service.
|
||||||
preserve_fn(self.appservice_handler.notify_interested_services)(
|
run_in_background(self._notify_app_services, room_stream_id)
|
||||||
room_stream_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.federation_sender:
|
if self.federation_sender:
|
||||||
self.federation_sender.notify_new_events(room_stream_id)
|
self.federation_sender.notify_new_events(room_stream_id)
|
||||||
@@ -266,6 +268,13 @@ class Notifier(object):
|
|||||||
rooms=[event.room_id],
|
rooms=[event.room_id],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _notify_app_services(self, room_stream_id):
|
||||||
|
try:
|
||||||
|
yield self.appservice_handler.notify_interested_services(room_stream_id)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error notifying application services of event")
|
||||||
|
|
||||||
def on_new_event(self, stream_key, new_token, users=[], rooms=[]):
|
def on_new_event(self, stream_key, new_token, users=[], rooms=[]):
|
||||||
""" Used to inform listeners that something has happend event wise.
|
""" Used to inform listeners that something has happend event wise.
|
||||||
|
|
||||||
@@ -330,11 +339,12 @@ class Notifier(object):
|
|||||||
# Now we wait for the _NotifierUserStream to be told there
|
# Now we wait for the _NotifierUserStream to be told there
|
||||||
# is a new token.
|
# is a new token.
|
||||||
listener = user_stream.new_listener(prev_token)
|
listener = user_stream.new_listener(prev_token)
|
||||||
|
add_timeout_to_deferred(
|
||||||
|
listener.deferred,
|
||||||
|
(end_time - now) / 1000.,
|
||||||
|
)
|
||||||
with PreserveLoggingContext():
|
with PreserveLoggingContext():
|
||||||
yield self.clock.time_bound_deferred(
|
yield listener.deferred
|
||||||
listener.deferred,
|
|
||||||
time_out=(end_time - now) / 1000.
|
|
||||||
)
|
|
||||||
|
|
||||||
current_token = user_stream.current_token
|
current_token = user_stream.current_token
|
||||||
|
|
||||||
@@ -345,7 +355,7 @@ class Notifier(object):
|
|||||||
# Update the prev_token to the current_token since nothing
|
# Update the prev_token to the current_token since nothing
|
||||||
# has happened between the old prev_token and the current_token
|
# has happened between the old prev_token and the current_token
|
||||||
prev_token = current_token
|
prev_token = current_token
|
||||||
except DeferredTimedOutError:
|
except DeferredTimeoutError:
|
||||||
break
|
break
|
||||||
except defer.CancelledError:
|
except defer.CancelledError:
|
||||||
break
|
break
|
||||||
@@ -550,13 +560,14 @@ class Notifier(object):
|
|||||||
if end_time <= now:
|
if end_time <= now:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
add_timeout_to_deferred(
|
||||||
|
listener.deferred.addTimeout,
|
||||||
|
(end_time - now) / 1000.,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
with PreserveLoggingContext():
|
with PreserveLoggingContext():
|
||||||
yield self.clock.time_bound_deferred(
|
yield listener.deferred
|
||||||
listener.deferred,
|
except DeferredTimeoutError:
|
||||||
time_out=(end_time - now) / 1000.
|
|
||||||
)
|
|
||||||
except DeferredTimedOutError:
|
|
||||||
break
|
break
|
||||||
except defer.CancelledError:
|
except defer.CancelledError:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ from synapse.state import POWER_KEY
|
|||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
|
from six import itervalues, iteritems
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -126,7 +127,7 @@ class BulkPushRuleEvaluator(object):
|
|||||||
)
|
)
|
||||||
auth_events = yield self.store.get_events(auth_events_ids)
|
auth_events = yield self.store.get_events(auth_events_ids)
|
||||||
auth_events = {
|
auth_events = {
|
||||||
(e.type, e.state_key): e for e in auth_events.itervalues()
|
(e.type, e.state_key): e for e in itervalues(auth_events)
|
||||||
}
|
}
|
||||||
|
|
||||||
sender_level = get_user_power_level(event.sender, auth_events)
|
sender_level = get_user_power_level(event.sender, auth_events)
|
||||||
@@ -160,7 +161,7 @@ class BulkPushRuleEvaluator(object):
|
|||||||
|
|
||||||
condition_cache = {}
|
condition_cache = {}
|
||||||
|
|
||||||
for uid, rules in rules_by_user.iteritems():
|
for uid, rules in iteritems(rules_by_user):
|
||||||
if event.sender == uid:
|
if event.sender == uid:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -406,7 +407,7 @@ class RulesForRoom(object):
|
|||||||
# If the event is a join event then it will be in current state evnts
|
# If the event is a join event then it will be in current state evnts
|
||||||
# map but not in the DB, so we have to explicitly insert it.
|
# map but not in the DB, so we have to explicitly insert it.
|
||||||
if event.type == EventTypes.Member:
|
if event.type == EventTypes.Member:
|
||||||
for event_id in member_event_ids.itervalues():
|
for event_id in itervalues(member_event_ids):
|
||||||
if event_id == event.event_id:
|
if event_id == event.event_id:
|
||||||
members[event_id] = (event.state_key, event.membership)
|
members[event_id] = (event.state_key, event.membership)
|
||||||
|
|
||||||
@@ -414,7 +415,7 @@ class RulesForRoom(object):
|
|||||||
logger.debug("Found members %r: %r", self.room_id, members.values())
|
logger.debug("Found members %r: %r", self.room_id, members.values())
|
||||||
|
|
||||||
interested_in_user_ids = set(
|
interested_in_user_ids = set(
|
||||||
user_id for user_id, membership in members.itervalues()
|
user_id for user_id, membership in itervalues(members)
|
||||||
if membership == Membership.JOIN
|
if membership == Membership.JOIN
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -426,7 +427,7 @@ class RulesForRoom(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
user_ids = set(
|
user_ids = set(
|
||||||
uid for uid, have_pusher in if_users_with_pushers.iteritems() if have_pusher
|
uid for uid, have_pusher in iteritems(if_users_with_pushers) if have_pusher
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug("With pushers: %r", user_ids)
|
logger.debug("With pushers: %r", user_ids)
|
||||||
@@ -447,7 +448,7 @@ class RulesForRoom(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
ret_rules_by_user.update(
|
ret_rules_by_user.update(
|
||||||
item for item in rules_by_user.iteritems() if item[0] is not None
|
item for item in iteritems(rules_by_user) if item[0] is not None
|
||||||
)
|
)
|
||||||
|
|
||||||
self.update_cache(sequence, members, ret_rules_by_user, state_group)
|
self.update_cache(sequence, members, ret_rules_by_user, state_group)
|
||||||
|
|||||||
@@ -77,10 +77,13 @@ class EmailPusher(object):
|
|||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_started(self):
|
def on_started(self):
|
||||||
if self.mailer is not None:
|
if self.mailer is not None:
|
||||||
self.throttle_params = yield self.store.get_throttle_params_by_room(
|
try:
|
||||||
self.pusher_id
|
self.throttle_params = yield self.store.get_throttle_params_by_room(
|
||||||
)
|
self.pusher_id
|
||||||
yield self._process()
|
)
|
||||||
|
yield self._process()
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error starting email pusher")
|
||||||
|
|
||||||
def on_stop(self):
|
def on_stop(self):
|
||||||
if self.timed_call:
|
if self.timed_call:
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import logging
|
|||||||
from twisted.internet import defer, reactor
|
from twisted.internet import defer, reactor
|
||||||
from twisted.internet.error import AlreadyCalled, AlreadyCancelled
|
from twisted.internet.error import AlreadyCalled, AlreadyCancelled
|
||||||
|
|
||||||
import push_rule_evaluator
|
from . import push_rule_evaluator
|
||||||
import push_tools
|
from . import push_tools
|
||||||
import synapse
|
import synapse
|
||||||
from synapse.push import PusherConfigException
|
from synapse.push import PusherConfigException
|
||||||
from synapse.util.logcontext import LoggingContext
|
from synapse.util.logcontext import LoggingContext
|
||||||
@@ -94,7 +94,10 @@ class HttpPusher(object):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_started(self):
|
def on_started(self):
|
||||||
yield self._process()
|
try:
|
||||||
|
yield self._process()
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error starting http pusher")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_new_notifications(self, min_stream_ordering, max_stream_ordering):
|
def on_new_notifications(self, min_stream_ordering, max_stream_ordering):
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ from synapse.types import UserID
|
|||||||
from synapse.util.caches import CACHE_SIZE_FACTOR, register_cache
|
from synapse.util.caches import CACHE_SIZE_FACTOR, register_cache
|
||||||
from synapse.util.caches.lrucache import LruCache
|
from synapse.util.caches.lrucache import LruCache
|
||||||
|
|
||||||
|
from six import string_types
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -238,7 +240,7 @@ def _flatten_dict(d, prefix=[], result=None):
|
|||||||
if result is None:
|
if result is None:
|
||||||
result = {}
|
result = {}
|
||||||
for key, value in d.items():
|
for key, value in d.items():
|
||||||
if isinstance(value, basestring):
|
if isinstance(value, string_types):
|
||||||
result[".".join(prefix + [key])] = value.lower()
|
result[".".join(prefix + [key])] = value.lower()
|
||||||
elif hasattr(value, "items"):
|
elif hasattr(value, "items"):
|
||||||
_flatten_dict(value, prefix=(prefix + [key]), result=result)
|
_flatten_dict(value, prefix=(prefix + [key]), result=result)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from httppusher import HttpPusher
|
from .httppusher import HttpPusher
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -14,13 +14,13 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from .pusher import PusherFactory
|
from synapse.push.pusher import PusherFactory
|
||||||
from synapse.util.logcontext import make_deferred_yieldable, preserve_fn
|
|
||||||
from synapse.util.async import run_on_reactor
|
from synapse.util.async import run_on_reactor
|
||||||
|
from synapse.util.logcontext import make_deferred_yieldable, run_in_background
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -137,12 +137,15 @@ class PusherPool:
|
|||||||
if u in self.pushers:
|
if u in self.pushers:
|
||||||
for p in self.pushers[u].values():
|
for p in self.pushers[u].values():
|
||||||
deferreds.append(
|
deferreds.append(
|
||||||
preserve_fn(p.on_new_notifications)(
|
run_in_background(
|
||||||
min_stream_id, max_stream_id
|
p.on_new_notifications,
|
||||||
|
min_stream_id, max_stream_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
yield make_deferred_yieldable(defer.gatherResults(deferreds))
|
yield make_deferred_yieldable(
|
||||||
|
defer.gatherResults(deferreds, consumeErrors=True),
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Exception in pusher on_new_notifications")
|
logger.exception("Exception in pusher on_new_notifications")
|
||||||
|
|
||||||
@@ -164,10 +167,15 @@ class PusherPool:
|
|||||||
if u in self.pushers:
|
if u in self.pushers:
|
||||||
for p in self.pushers[u].values():
|
for p in self.pushers[u].values():
|
||||||
deferreds.append(
|
deferreds.append(
|
||||||
preserve_fn(p.on_new_receipts)(min_stream_id, max_stream_id)
|
run_in_background(
|
||||||
|
p.on_new_receipts,
|
||||||
|
min_stream_id, max_stream_id,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
yield make_deferred_yieldable(defer.gatherResults(deferreds))
|
yield make_deferred_yieldable(
|
||||||
|
defer.gatherResults(deferreds, consumeErrors=True),
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Exception in pusher on_new_receipts")
|
logger.exception("Exception in pusher on_new_receipts")
|
||||||
|
|
||||||
@@ -207,7 +215,7 @@ class PusherPool:
|
|||||||
if appid_pushkey in byuser:
|
if appid_pushkey in byuser:
|
||||||
byuser[appid_pushkey].on_stop()
|
byuser[appid_pushkey].on_stop()
|
||||||
byuser[appid_pushkey] = p
|
byuser[appid_pushkey] = p
|
||||||
preserve_fn(p.on_started)()
|
run_in_background(p.on_started)
|
||||||
|
|
||||||
logger.info("Started pushers")
|
logger.info("Started pushers")
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Copyright 2015, 2016 OpenMarket Ltd
|
# Copyright 2015, 2016 OpenMarket Ltd
|
||||||
# Copyright 2017 Vector Creations Ltd
|
# Copyright 2017 Vector Creations Ltd
|
||||||
|
# Copyright 2018 New Vector Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -18,16 +19,31 @@ from distutils.version import LooseVersion
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# this dict maps from python package name to a list of modules we expect it to
|
||||||
|
# provide.
|
||||||
|
#
|
||||||
|
# the key is a "requirement specifier", as used as a parameter to `pip
|
||||||
|
# install`[1], or an `install_requires` argument to `setuptools.setup` [2].
|
||||||
|
#
|
||||||
|
# the value is a sequence of strings; each entry should be the name of the
|
||||||
|
# python module, optionally followed by a version assertion which can be either
|
||||||
|
# ">=<ver>" or "==<ver>".
|
||||||
|
#
|
||||||
|
# [1] https://pip.pypa.io/en/stable/reference/pip_install/#requirement-specifiers.
|
||||||
|
# [2] https://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-dependencies
|
||||||
REQUIREMENTS = {
|
REQUIREMENTS = {
|
||||||
"jsonschema>=2.5.1": ["jsonschema>=2.5.1"],
|
"jsonschema>=2.5.1": ["jsonschema>=2.5.1"],
|
||||||
"frozendict>=0.4": ["frozendict"],
|
"frozendict>=0.4": ["frozendict"],
|
||||||
"unpaddedbase64>=1.1.0": ["unpaddedbase64>=1.1.0"],
|
"unpaddedbase64>=1.1.0": ["unpaddedbase64>=1.1.0"],
|
||||||
"canonicaljson>=1.0.0": ["canonicaljson>=1.0.0"],
|
"canonicaljson>=1.1.3": ["canonicaljson>=1.1.3"],
|
||||||
"signedjson>=1.0.0": ["signedjson>=1.0.0"],
|
"signedjson>=1.0.0": ["signedjson>=1.0.0"],
|
||||||
"pynacl>=1.2.1": ["nacl>=1.2.1", "nacl.bindings"],
|
"pynacl>=1.2.1": ["nacl>=1.2.1", "nacl.bindings"],
|
||||||
"service_identity>=1.0.0": ["service_identity>=1.0.0"],
|
"service_identity>=1.0.0": ["service_identity>=1.0.0"],
|
||||||
"Twisted>=16.0.0": ["twisted>=16.0.0"],
|
"Twisted>=16.0.0": ["twisted>=16.0.0"],
|
||||||
"pyopenssl>=0.14": ["OpenSSL>=0.14"],
|
|
||||||
|
# We use crypto.get_elliptic_curve which is only supported in >=0.15
|
||||||
|
"pyopenssl>=0.15": ["OpenSSL>=0.15"],
|
||||||
|
|
||||||
"pyyaml": ["yaml"],
|
"pyyaml": ["yaml"],
|
||||||
"pyasn1": ["pyasn1"],
|
"pyasn1": ["pyasn1"],
|
||||||
"daemonize": ["daemonize"],
|
"daemonize": ["daemonize"],
|
||||||
@@ -39,6 +55,7 @@ REQUIREMENTS = {
|
|||||||
"pymacaroons-pynacl": ["pymacaroons"],
|
"pymacaroons-pynacl": ["pymacaroons"],
|
||||||
"msgpack-python>=0.3.0": ["msgpack"],
|
"msgpack-python>=0.3.0": ["msgpack"],
|
||||||
"phonenumbers>=8.2.0": ["phonenumbers"],
|
"phonenumbers>=8.2.0": ["phonenumbers"],
|
||||||
|
"six": ["six"],
|
||||||
}
|
}
|
||||||
CONDITIONAL_REQUIREMENTS = {
|
CONDITIONAL_REQUIREMENTS = {
|
||||||
"web_client": {
|
"web_client": {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user