Codebase list matrix-synapse / f509bf3
New upstream version 1.24.0 Andrej Shadura 3 years ago
155 changed file(s) with 4726 addition(s) and 2620 deletion(s). Raw diff Collapse all Expand all
55 set -ex
66
77 apt-get update
8 apt-get install -y python3.5 python3.5-dev python3-pip libxml2-dev libxslt-dev zlib1g-dev tox
8 apt-get install -y python3.5 python3.5-dev python3-pip libxml2-dev libxslt-dev xmlsec1 zlib1g-dev tox
99
1010 export LANG="C.UTF-8"
1111
0 Synapse 1.24.0 (2020-12-09)
1 ===========================
2
3 Due to the two security issues highlighted below, server administrators are
4 encouraged to update Synapse. We are not aware of these vulnerabilities being
5 exploited in the wild.
6
7 Security advisory
8 -----------------
9
10 The following issues are fixed in v1.23.1 and v1.24.0.
11
12 - There is a denial of service attack
13 ([CVE-2020-26257](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-26257))
14 against the federation APIs in which future events will not be correctly sent
15 to other servers over federation. This affects all servers that participate in
16 open federation. (Fixed in [#8776](https://github.com/matrix-org/synapse/pull/8776)).
17
18 - Synapse may be affected by OpenSSL
19 [CVE-2020-1971](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1971).
20 Synapse administrators should ensure that they have the latest versions of
21 the cryptography Python package installed.
22
23 To upgrade Synapse along with the cryptography package:
24
25 * Administrators using the [`matrix.org` Docker
26 image](https://hub.docker.com/r/matrixdotorg/synapse/) or the [Debian/Ubuntu
27 packages from
28 `matrix.org`](https://github.com/matrix-org/synapse/blob/master/INSTALL.md#matrixorg-packages)
29 should ensure that they have version 1.24.0 or 1.23.1 installed: these images include
30 the updated packages.
31 * Administrators who have [installed Synapse from
32 source](https://github.com/matrix-org/synapse/blob/master/INSTALL.md#installing-from-source)
33 should upgrade the cryptography package within their virtualenv by running:
34 ```sh
35 <path_to_virtualenv>/bin/pip install 'cryptography>=3.3'
36 ```
37 * Administrators who have installed Synapse from distribution packages should
38 consult the information from their distributions.
39
40 Internal Changes
41 ----------------
42
43 - Add a maximum version for pysaml2 on Python 3.5. ([\#8898](https://github.com/matrix-org/synapse/issues/8898))
44
45
46 Synapse 1.24.0rc2 (2020-12-04)
47 ==============================
48
49 Bugfixes
50 --------
51
52 - Fix a regression in v1.24.0rc1 which failed to allow SAML mapping providers which were unable to redirect users to an additional page. ([\#8878](https://github.com/matrix-org/synapse/issues/8878))
53
54
55 Internal Changes
56 ----------------
57
58 - Add support for the `prometheus_client` newer than 0.9.0. Contributed by Jordan Bancino. ([\#8875](https://github.com/matrix-org/synapse/issues/8875))
59
60
61 Synapse 1.24.0rc1 (2020-12-02)
62 ==============================
63
64 Features
65 --------
66
67 - Add admin API for logging in as a user. ([\#8617](https://github.com/matrix-org/synapse/issues/8617))
68 - Allow specification of the SAML IdP if the metadata returns multiple IdPs. ([\#8630](https://github.com/matrix-org/synapse/issues/8630))
69 - Add support for re-trying generation of a localpart for OpenID Connect mapping providers. ([\#8801](https://github.com/matrix-org/synapse/issues/8801), [\#8855](https://github.com/matrix-org/synapse/issues/8855))
70 - Allow the `Date` header through CORS. Contributed by Nicolas Chamo. ([\#8804](https://github.com/matrix-org/synapse/issues/8804))
71 - Add a config option, `push.group_by_unread_count`, which controls whether unread message counts in push notifications are defined as "the number of rooms with unread messages" or "total unread messages". ([\#8820](https://github.com/matrix-org/synapse/issues/8820))
72 - Add `force_purge` option to delete-room admin api. ([\#8843](https://github.com/matrix-org/synapse/issues/8843))
73
74
75 Bugfixes
76 --------
77
78 - Fix a bug where appservices may be sent an excessive amount of read receipts and presence. Broke in v1.22.0. ([\#8744](https://github.com/matrix-org/synapse/issues/8744))
79 - Fix a bug in some federation APIs which could lead to unexpected behaviour if different parameters were set in the URI and the request body. ([\#8776](https://github.com/matrix-org/synapse/issues/8776))
80 - Fix a bug where synctl could spawn duplicate copies of a worker. Contributed by Waylon Cude. ([\#8798](https://github.com/matrix-org/synapse/issues/8798))
81 - Allow per-room profiles to be used for the server notice user. ([\#8799](https://github.com/matrix-org/synapse/issues/8799))
82 - Fix a bug where logging could break after a call to SIGHUP. ([\#8817](https://github.com/matrix-org/synapse/issues/8817))
83 - Fix `register_new_matrix_user` failing with "Bad Request" when trailing slash is included in server URL. Contributed by @angdraug. ([\#8823](https://github.com/matrix-org/synapse/issues/8823))
84 - Fix a minor long-standing bug in login, where we would offer the `password` login type if a custom auth provider supported it, even if password login was disabled. ([\#8835](https://github.com/matrix-org/synapse/issues/8835))
85 - Fix a long-standing bug which caused Synapse to require unspecified parameters during user-interactive authentication. ([\#8848](https://github.com/matrix-org/synapse/issues/8848))
86 - Fix a bug introduced in v1.20.0 where the user-agent and IP address reported during user registration for CAS, OpenID Connect, and SAML were of the wrong form. ([\#8784](https://github.com/matrix-org/synapse/issues/8784))
87
88
89 Improved Documentation
90 ----------------------
91
92 - Clarify the usecase for a msisdn delegate. Contributed by Adrian Wannenmacher. ([\#8734](https://github.com/matrix-org/synapse/issues/8734))
93 - Remove extraneous comma from JSON example in User Admin API docs. ([\#8771](https://github.com/matrix-org/synapse/issues/8771))
94 - Update `turn-howto.md` with troubleshooting notes. ([\#8779](https://github.com/matrix-org/synapse/issues/8779))
95 - Fix the example on how to set the `Content-Type` header in nginx for the Client Well-Known URI. ([\#8793](https://github.com/matrix-org/synapse/issues/8793))
96 - Improve the documentation for the admin API to list all media in a room with respect to encrypted events. ([\#8795](https://github.com/matrix-org/synapse/issues/8795))
97 - Update the formatting of the `push` section of the homeserver config file to better align with the [code style guidelines](https://github.com/matrix-org/synapse/blob/develop/docs/code_style.md#configuration-file-format). ([\#8818](https://github.com/matrix-org/synapse/issues/8818))
98 - Improve documentation how to configure prometheus for workers. ([\#8822](https://github.com/matrix-org/synapse/issues/8822))
99 - Update example prometheus console. ([\#8824](https://github.com/matrix-org/synapse/issues/8824))
100
101
102 Deprecations and Removals
103 -------------------------
104
105 - Remove old `/_matrix/client/*/admin` endpoints which were deprecated since Synapse 1.20.0. ([\#8785](https://github.com/matrix-org/synapse/issues/8785))
106 - Disable pretty printing JSON responses for curl. Users who want pretty-printed output should use [jq](https://stedolan.github.io/jq/) in combination with curl. Contributed by @tulir. ([\#8833](https://github.com/matrix-org/synapse/issues/8833))
107
108
109 Internal Changes
110 ----------------
111
112 - Simplify the way the `HomeServer` object caches its internal attributes. ([\#8565](https://github.com/matrix-org/synapse/issues/8565), [\#8851](https://github.com/matrix-org/synapse/issues/8851))
113 - Add an example and documentation for clock skew to the SAML2 sample configuration to allow for clock/time difference between the homserver and IdP. Contributed by @localguru. ([\#8731](https://github.com/matrix-org/synapse/issues/8731))
114 - Generalise `RoomMemberHandler._locally_reject_invite` to apply to more flows than just invite. ([\#8751](https://github.com/matrix-org/synapse/issues/8751))
115 - Generalise `RoomStore.maybe_store_room_on_invite` to handle other, non-invite membership events. ([\#8754](https://github.com/matrix-org/synapse/issues/8754))
116 - Refactor test utilities for injecting HTTP requests. ([\#8757](https://github.com/matrix-org/synapse/issues/8757), [\#8758](https://github.com/matrix-org/synapse/issues/8758), [\#8759](https://github.com/matrix-org/synapse/issues/8759), [\#8760](https://github.com/matrix-org/synapse/issues/8760), [\#8761](https://github.com/matrix-org/synapse/issues/8761), [\#8777](https://github.com/matrix-org/synapse/issues/8777))
117 - Consolidate logic between the OpenID Connect and SAML code. ([\#8765](https://github.com/matrix-org/synapse/issues/8765))
118 - Use `TYPE_CHECKING` instead of magic `MYPY` variable. ([\#8770](https://github.com/matrix-org/synapse/issues/8770))
119 - Add a commandline script to sign arbitrary json objects. ([\#8772](https://github.com/matrix-org/synapse/issues/8772))
120 - Minor log line improvements for the SSO mapping code used to generate Matrix IDs from SSO IDs. ([\#8773](https://github.com/matrix-org/synapse/issues/8773))
121 - Add additional error checking for OpenID Connect and SAML mapping providers. ([\#8774](https://github.com/matrix-org/synapse/issues/8774), [\#8800](https://github.com/matrix-org/synapse/issues/8800))
122 - Add type hints to HTTP abstractions. ([\#8806](https://github.com/matrix-org/synapse/issues/8806), [\#8812](https://github.com/matrix-org/synapse/issues/8812))
123 - Remove unnecessary function arguments and add typing to several membership replication classes. ([\#8809](https://github.com/matrix-org/synapse/issues/8809))
124 - Optimise the lookup for an invite from another homeserver when trying to reject it. ([\#8815](https://github.com/matrix-org/synapse/issues/8815))
125 - Add tests for `password_auth_provider`s. ([\#8819](https://github.com/matrix-org/synapse/issues/8819))
126 - Drop redundant database index on `event_json`. ([\#8845](https://github.com/matrix-org/synapse/issues/8845))
127 - Simplify `uk.half-shot.msc2778.login.application_service` login handler. ([\#8847](https://github.com/matrix-org/synapse/issues/8847))
128 - Refactor `password_auth_provider` support code. ([\#8849](https://github.com/matrix-org/synapse/issues/8849))
129 - Add missing `ordering` to background database updates. ([\#8850](https://github.com/matrix-org/synapse/issues/8850))
130 - Allow for specifying a room version when creating a room in unit tests via `RestHelper.create_room_as`. ([\#8854](https://github.com/matrix-org/synapse/issues/8854))
131
132
0133 Synapse 1.23.0 (2020-11-18)
1134 ===========================
2135
63216454
63226455 See UPGRADES.rst for specific instructions on how to upgrade.
63236456
6324 > - Fix bug where we served up an Event that did not match its signatures.
6325 > - Fix regression where we no longer correctly handled the case where a homeserver receives an event for a room it doesn\'t recognise (but is in.)
6457 - Fix bug where we served up an Event that did not match its signatures.
6458 - Fix regression where we no longer correctly handled the case where a homeserver receives an event for a room it doesn\'t recognise (but is in.)
63266459
63276460 Changes in synapse 0.5.0 (2014-11-19)
63286461 =====================================
63336466
63346467 Homeserver:
63356468
6336 : - Add authentication and authorization to the federation protocol. Events are now signed by their originating homeservers.
6337 - Implement the new authorization model for rooms.
6338 - Split out web client into a seperate repository: matrix-angular-sdk.
6339 - Change the structure of PDUs.
6340 - Fix bug where user could not join rooms via an alias containing 4-byte UTF-8 characters.
6341 - Merge concept of PDUs and Events internally.
6342 - Improve logging by adding request ids to log lines.
6343 - Implement a very basic room initial sync API.
6344 - Implement the new invite/join federation APIs.
6469 - Add authentication and authorization to the federation protocol. Events are now signed by their originating homeservers.
6470 - Implement the new authorization model for rooms.
6471 - Split out web client into a seperate repository: matrix-angular-sdk.
6472 - Change the structure of PDUs.
6473 - Fix bug where user could not join rooms via an alias containing 4-byte UTF-8 characters.
6474 - Merge concept of PDUs and Events internally.
6475 - Improve logging by adding request ids to log lines.
6476 - Implement a very basic room initial sync API.
6477 - Implement the new invite/join federation APIs.
63456478
63466479 Webclient:
63476480
6348 : - The webclient has been moved to a seperate repository.
6481 - The webclient has been moved to a seperate repository.
63496482
63506483 Changes in synapse 0.4.2 (2014-10-31)
63516484 =====================================
63526485
63536486 Homeserver:
63546487
6355 : - Fix bugs where we did not notify users of correct presence updates.
6356 - Fix bug where we did not handle sub second event stream timeouts.
6488 - Fix bugs where we did not notify users of correct presence updates.
6489 - Fix bug where we did not handle sub second event stream timeouts.
63576490
63586491 Webclient:
63596492
6360 : - Add ability to click on messages to see JSON.
6361 - Add ability to redact messages.
6362 - Add ability to view and edit all room state JSON.
6363 - Handle incoming redactions.
6364 - Improve feedback on errors.
6365 - Fix bugs in mobile CSS.
6366 - Fix bugs with desktop notifications.
6493 - Add ability to click on messages to see JSON.
6494 - Add ability to redact messages.
6495 - Add ability to view and edit all room state JSON.
6496 - Handle incoming redactions.
6497 - Improve feedback on errors.
6498 - Fix bugs in mobile CSS.
6499 - Fix bugs with desktop notifications.
63676500
63686501 Changes in synapse 0.4.1 (2014-10-17)
63696502 =====================================
63706503
63716504 Webclient:
63726505
6373 : - Fix bug with display of timestamps.
6506 - Fix bug with display of timestamps.
63746507
63756508 Changes in synpase 0.4.0 (2014-10-17)
63766509 =====================================
63836516
63846517 Homeserver:
63856518
6386 : - Sign federation transactions to assert strong identity over federation.
6387 - Rename timestamp keys in PDUs and events from \'ts\' and \'hsob\_ts\' to \'origin\_server\_ts\'.
6519 - Sign federation transactions to assert strong identity over federation.
6520 - Rename timestamp keys in PDUs and events from \'ts\' and \'hsob\_ts\' to \'origin\_server\_ts\'.
63886521
63896522 Changes in synapse 0.3.4 (2014-09-25)
63906523 =====================================
63936526
63946527 Homeserver:
63956528
6396 : - Add support for redaction of messages.
6397 - Fix bug where inviting a user on a remote home server could take up to 20-30s.
6398 - Implement a get current room state API.
6399 - Add support specifying and retrieving turn server configuration.
6529 - Add support for redaction of messages.
6530 - Fix bug where inviting a user on a remote home server could take up to 20-30s.
6531 - Implement a get current room state API.
6532 - Add support specifying and retrieving turn server configuration.
64006533
64016534 Webclient:
64026535
6403 : - Add button to send messages to users from the home page.
6404 - Add support for using TURN for VoIP calls.
6405 - Show display name change messages.
6406 - Fix bug where the client didn\'t get the state of a newly joined room until after it has been refreshed.
6407 - Fix bugs with tab complete.
6408 - Fix bug where holding down the down arrow caused chrome to chew 100% CPU.
6409 - Fix bug where desktop notifications occasionally used \"Undefined\" as the display name.
6410 - Fix more places where we sometimes saw room IDs incorrectly.
6411 - Fix bug which caused lag when entering text in the text box.
6536 - Add button to send messages to users from the home page.
6537 - Add support for using TURN for VoIP calls.
6538 - Show display name change messages.
6539 - Fix bug where the client didn\'t get the state of a newly joined room until after it has been refreshed.
6540 - Fix bugs with tab complete.
6541 - Fix bug where holding down the down arrow caused chrome to chew 100% CPU.
6542 - Fix bug where desktop notifications occasionally used \"Undefined\" as the display name.
6543 - Fix more places where we sometimes saw room IDs incorrectly.
6544 - Fix bug which caused lag when entering text in the text box.
64126545
64136546 Changes in synapse 0.3.3 (2014-09-22)
64146547 =====================================
64156548
64166549 Homeserver:
64176550
6418 : - Fix bug where you continued to get events for rooms you had left.
6551 - Fix bug where you continued to get events for rooms you had left.
64196552
64206553 Webclient:
64216554
6422 : - Add support for video calls with basic UI.
6423 - Fix bug where one to one chats were named after your display name rather than the other person\'s.
6424 - Fix bug which caused lag when typing in the textarea.
6425 - Refuse to run on browsers we know won\'t work.
6426 - Trigger pagination when joining new rooms.
6427 - Fix bug where we sometimes didn\'t display invitations in recents.
6428 - Automatically join room when accepting a VoIP call.
6429 - Disable outgoing and reject incoming calls on browsers we don\'t support VoIP in.
6430 - Don\'t display desktop notifications for messages in the room you are non-idle and speaking in.
6555 - Add support for video calls with basic UI.
6556 - Fix bug where one to one chats were named after your display name rather than the other person\'s.
6557 - Fix bug which caused lag when typing in the textarea.
6558 - Refuse to run on browsers we know won\'t work.
6559 - Trigger pagination when joining new rooms.
6560 - Fix bug where we sometimes didn\'t display invitations in recents.
6561 - Automatically join room when accepting a VoIP call.
6562 - Disable outgoing and reject incoming calls on browsers we don\'t support VoIP in.
6563 - Don\'t display desktop notifications for messages in the room you are non-idle and speaking in.
64316564
64326565 Changes in synapse 0.3.2 (2014-09-18)
64336566 =====================================
64346567
64356568 Webclient:
64366569
6437 : - Fix bug where an empty \"bing words\" list in old accounts didn\'t send notifications when it should have done.
6570 - Fix bug where an empty \"bing words\" list in old accounts didn\'t send notifications when it should have done.
64386571
64396572 Changes in synapse 0.3.1 (2014-09-18)
64406573 =====================================
64436576
64446577 Webclient:
64456578
6446 : - Fix a regression where we sometimes displayed duplicate events.
6447 - Fix a regression where we didn\'t immediately remove rooms you were banned in from the recents list.
6579 - Fix a regression where we sometimes displayed duplicate events.
6580 - Fix a regression where we didn\'t immediately remove rooms you were banned in from the recents list.
64486581
64496582 Changes in synapse 0.3.0 (2014-09-18)
64506583 =====================================
64536586
64546587 Homeserver:
64556588
6456 : - When a user changes their displayname or avatar the server will now update all their join states to reflect this.
6457 - The server now adds \"age\" key to events to indicate how old they are. This is clock independent, so at no point does any server or webclient have to assume their clock is in sync with everyone else.
6458 - Fix bug where we didn\'t correctly pull in missing PDUs.
6459 - Fix bug where prev\_content key wasn\'t always returned.
6460 - Add support for password resets.
6589 - When a user changes their displayname or avatar the server will now update all their join states to reflect this.
6590 - The server now adds \"age\" key to events to indicate how old they are. This is clock independent, so at no point does any server or webclient have to assume their clock is in sync with everyone else.
6591 - Fix bug where we didn\'t correctly pull in missing PDUs.
6592 - Fix bug where prev\_content key wasn\'t always returned.
6593 - Add support for password resets.
64616594
64626595 Webclient:
64636596
6464 : - Improve page content loading.
6465 - Join/parts now trigger desktop notifications.
6466 - Always show room aliases in the UI if one is present.
6467 - No longer show user-count in the recents side panel.
6468 - Add up & down arrow support to the text box for message sending to step through your sent history.
6469 - Don\'t display notifications for our own messages.
6470 - Emotes are now formatted correctly in desktop notifications.
6471 - The recents list now differentiates between public & private rooms.
6472 - Fix bug where when switching between rooms the pagination flickered before the view jumped to the bottom of the screen.
6473 - Add bing word support.
6597 - Improve page content loading.
6598 - Join/parts now trigger desktop notifications.
6599 - Always show room aliases in the UI if one is present.
6600 - No longer show user-count in the recents side panel.
6601 - Add up & down arrow support to the text box for message sending to step through your sent history.
6602 - Don\'t display notifications for our own messages.
6603 - Emotes are now formatted correctly in desktop notifications.
6604 - The recents list now differentiates between public & private rooms.
6605 - Fix bug where when switching between rooms the pagination flickered before the view jumped to the bottom of the screen.
6606 - Add bing word support.
64746607
64756608 Registration API:
64766609
6477 : - The registration API has been overhauled to function like the login API. In practice, this means registration requests must now include the following: \'type\':\'m.login.password\'. See UPGRADE for more information on this.
6478 - The \'user\_id\' key has been renamed to \'user\' to better match the login API.
6479 - There is an additional login type: \'m.login.email.identity\'.
6480 - The command client and web client have been updated to reflect these changes.
6610 - The registration API has been overhauled to function like the login API. In practice, this means registration requests must now include the following: \'type\':\'m.login.password\'. See UPGRADE for more information on this.
6611 - The \'user\_id\' key has been renamed to \'user\' to better match the login API.
6612 - There is an additional login type: \'m.login.email.identity\'.
6613 - The command client and web client have been updated to reflect these changes.
64816614
64826615 Changes in synapse 0.2.3 (2014-09-12)
64836616 =====================================
64846617
64856618 Homeserver:
64866619
6487 : - Fix bug where we stopped sending events to remote home servers if a user from that home server left, even if there were some still in the room.
6488 - Fix bugs in the state conflict resolution where it was incorrectly rejecting events.
6620 - Fix bug where we stopped sending events to remote home servers if a user from that home server left, even if there were some still in the room.
6621 - Fix bugs in the state conflict resolution where it was incorrectly rejecting events.
64896622
64906623 Webclient:
64916624
6492 : - Display room names and topics.
6493 - Allow setting/editing of room names and topics.
6494 - Display information about rooms on the main page.
6495 - Handle ban and kick events in real time.
6496 - VoIP UI and reliability improvements.
6497 - Add glare support for VoIP.
6498 - Improvements to initial startup speed.
6499 - Don\'t display duplicate join events.
6500 - Local echo of messages.
6501 - Differentiate sending and sent of local echo.
6502 - Various minor bug fixes.
6625 - Display room names and topics.
6626 - Allow setting/editing of room names and topics.
6627 - Display information about rooms on the main page.
6628 - Handle ban and kick events in real time.
6629 - VoIP UI and reliability improvements.
6630 - Add glare support for VoIP.
6631 - Improvements to initial startup speed.
6632 - Don\'t display duplicate join events.
6633 - Local echo of messages.
6634 - Differentiate sending and sent of local echo.
6635 - Various minor bug fixes.
65036636
65046637 Changes in synapse 0.2.2 (2014-09-06)
65056638 =====================================
65066639
65076640 Homeserver:
65086641
6509 : - When the server returns state events it now also includes the previous content.
6510 - Add support for inviting people when creating a new room.
6511 - Make the homeserver inform the room via m.room.aliases when a new alias is added for a room.
6512 - Validate m.room.power\_level events.
6642 - When the server returns state events it now also includes the previous content.
6643 - Add support for inviting people when creating a new room.
6644 - Make the homeserver inform the room via m.room.aliases when a new alias is added for a room.
6645 - Validate m.room.power\_level events.
65136646
65146647 Webclient:
65156648
6516 : - Add support for captchas on registration.
6517 - Handle m.room.aliases events.
6518 - Asynchronously send messages and show a local echo.
6519 - Inform the UI when a message failed to send.
6520 - Only autoscroll on receiving a new message if the user was already at the bottom of the screen.
6521 - Add support for ban/kick reasons.
6649 - Add support for captchas on registration.
6650 - Handle m.room.aliases events.
6651 - Asynchronously send messages and show a local echo.
6652 - Inform the UI when a message failed to send.
6653 - Only autoscroll on receiving a new message if the user was already at the bottom of the screen.
6654 - Add support for ban/kick reasons.
65226655
65236656 Changes in synapse 0.2.1 (2014-09-03)
65246657 =====================================
65256658
65266659 Homeserver:
65276660
6528 : - Added support for signing up with a third party id.
6529 - Add synctl scripts.
6530 - Added rate limiting.
6531 - Add option to change the external address the content repo uses.
6532 - Presence bug fixes.
6661 - Added support for signing up with a third party id.
6662 - Add synctl scripts.
6663 - Added rate limiting.
6664 - Add option to change the external address the content repo uses.
6665 - Presence bug fixes.
65336666
65346667 Webclient:
65356668
6536 : - Added support for signing up with a third party id.
6537 - Added support for banning and kicking users.
6538 - Added support for displaying and setting ops.
6539 - Added support for room names.
6540 - Fix bugs with room membership event display.
6669 - Added support for signing up with a third party id.
6670 - Added support for banning and kicking users.
6671 - Added support for displaying and setting ops.
6672 - Added support for room names.
6673 - Fix bugs with room membership event display.
65416674
65426675 Changes in synapse 0.2.0 (2014-09-02)
65436676 =====================================
65466679
65476680 Homeserver:
65486681
6549 : - Require SSL for server-server connections.
6550 - Add SSL listener for client-server connections.
6551 - Add ability to use config files.
6552 - Add support for kicking/banning and power levels.
6553 - Allow setting of room names and topics on creation.
6554 - Change presence to include last seen time of the user.
6555 - Change url path prefix to /\_matrix/\...
6556 - Bug fixes to presence.
6682 - Require SSL for server-server connections.
6683 - Add SSL listener for client-server connections.
6684 - Add ability to use config files.
6685 - Add support for kicking/banning and power levels.
6686 - Allow setting of room names and topics on creation.
6687 - Change presence to include last seen time of the user.
6688 - Change url path prefix to /\_matrix/\...
6689 - Bug fixes to presence.
65576690
65586691 Webclient:
65596692
6560 : - Reskin the CSS for registration and login.
6561 - Various improvements to rooms CSS.
6562 - Support changes in client-server API.
6563 - Bug fixes to VOIP UI.
6564 - Various bug fixes to handling of changes to room member list.
6693 - Reskin the CSS for registration and login.
6694 - Various improvements to rooms CSS.
6695 - Support changes in client-server API.
6696 - Bug fixes to VOIP UI.
6697 - Various bug fixes to handling of changes to room member list.
65656698
65666699 Changes in synapse 0.1.2 (2014-08-29)
65676700 =====================================
65686701
65696702 Webclient:
65706703
6571 : - Add basic call state UI for VoIP calls.
6704 - Add basic call state UI for VoIP calls.
65726705
65736706 Changes in synapse 0.1.1 (2014-08-29)
65746707 =====================================
65756708
65766709 Homeserver:
65776710
6578 : - Fix bug that caused the event stream to not notify some clients about changes.
6711 - Fix bug that caused the event stream to not notify some clients about changes.
65796712
65806713 Changes in synapse 0.1.0 (2014-08-29)
65816714 =====================================
65846717
65856718 Homeserver:
65866719
6587 : -
6588
6589 Update client to server API, including:
6590
6591 : - Use a more consistent url scheme.
6592 - Provide more useful information in the initial sync api.
6593
6594 - Change the presence handling to be much more efficient.
6595 - Change the presence server to server API to not require explicit polling of all users who share a room with a user.
6596 - Fix races in the event streaming logic.
6720 - Update client to server API, including:
6721 - Use a more consistent url scheme.
6722 - Provide more useful information in the initial sync api.
6723 - Change the presence handling to be much more efficient.
6724 - Change the presence server to server API to not require explicit polling of all users who share a room with a user.
6725 - Fix races in the event streaming logic.
65976726
65986727 Webclient:
65996728
6600 : - Update to use new client to server API.
6601 - Add basic VOIP support.
6602 - Add idle timers that change your status to away.
6603 - Add recent rooms column when viewing a room.
6604 - Various network efficiency improvements.
6605 - Add basic mobile browser support.
6606 - Add a settings page.
6729 - Update to use new client to server API.
6730 - Add basic VOIP support.
6731 - Add idle timers that change your status to away.
6732 - Add recent rooms column when viewing a room.
6733 - Various network efficiency improvements.
6734 - Add basic mobile browser support.
6735 - Add a settings page.
66076736
66086737 Changes in synapse 0.0.1 (2014-08-22)
66096738 =====================================
66126741
66136742 Homeserver:
66146743
6615 : - Completely change the database schema to support generic event types.
6616 - Improve presence reliability.
6617 - Improve reliability of joining remote rooms.
6618 - Fix bug where room join events were duplicated.
6619 - Improve initial sync API to return more information to the client.
6620 - Stop generating fake messages for room membership events.
6744 - Completely change the database schema to support generic event types.
6745 - Improve presence reliability.
6746 - Improve reliability of joining remote rooms.
6747 - Fix bug where room join events were duplicated.
6748 - Improve initial sync API to return more information to the client.
6749 - Stop generating fake messages for room membership events.
66216750
66226751 Webclient:
66236752
6624 : - Add tab completion of names.
6625 - Add ability to upload and send images.
6626 - Add profile pages.
6627 - Improve CSS layout of room.
6628 - Disambiguate identical display names.
6629 - Don\'t get remote users display names and avatars individually.
6630 - Use the new initial sync API to reduce number of round trips to the homeserver.
6631 - Change url scheme to use room aliases instead of room ids where known.
6632 - Increase longpoll timeout.
6753 - Add tab completion of names.
6754 - Add ability to upload and send images.
6755 - Add profile pages.
6756 - Improve CSS layout of room.
6757 - Disambiguate identical display names.
6758 - Don\'t get remote users display names and avatars individually.
6759 - Use the new initial sync API to reduce number of round trips to the homeserver.
6760 - Change url scheme to use room aliases instead of room ids where known.
6761 - Increase longpoll timeout.
66336762
66346763 Changes in synapse 0.0.0 (2014-08-13)
66356764 =====================================
66366765
6637 > - Initial alpha release
6766 - Initial alpha release
486486 ```
487487 location /.well-known/matrix/client {
488488 return 200 '{"m.homeserver": {"base_url": "https://<matrix.example.com>"}}';
489 add_header Content-Type application/json;
489 default_type application/json;
490490 add_header Access-Control-Allow-Origin *;
491491 }
492492 ```
7373 # replace `1.3.0` and `stretch` accordingly:
7474 wget https://packages.matrix.org/debian/pool/main/m/matrix-synapse-py3/matrix-synapse-py3_1.3.0+stretch1_amd64.deb
7575 dpkg -i matrix-synapse-py3_1.3.0+stretch1_amd64.deb
76
77 Upgrading to v1.24.0
78 ====================
79
80 Custom OpenID Connect mapping provider breaking change
81 ------------------------------------------------------
82
83 This release allows the OpenID Connect mapping provider to perform normalisation
84 of the localpart of the Matrix ID. This allows for the mapping provider to
85 specify different algorithms, instead of the [default way](https://matrix.org/docs/spec/appendices#mapping-from-other-character-sets).
86
87 If your Synapse configuration uses a custom mapping provider
88 (`oidc_config.user_mapping_provider.module` is specified and not equal to
89 `synapse.handlers.oidc_handler.JinjaOidcMappingProvider`) then you *must* ensure
90 that `map_user_attributes` of the mapping provider performs some normalisation
91 of the `localpart` returned. To match previous behaviour you can use the
92 `map_username_to_mxid_localpart` function provided by Synapse. An example is
93 shown below:
94
95 .. code-block:: python
96
97 from synapse.types import map_username_to_mxid_localpart
98
99 class MyMappingProvider:
100 def map_user_attributes(self, userinfo, token):
101 # ... your custom logic ...
102 sso_user_id = ...
103 localpart = map_username_to_mxid_localpart(sso_user_id)
104
105 return {"localpart": localpart}
106
107 Removal historical Synapse Admin API
108 ------------------------------------
109
110 Historically, the Synapse Admin API has been accessible under:
111
112 * ``/_matrix/client/api/v1/admin``
113 * ``/_matrix/client/unstable/admin``
114 * ``/_matrix/client/r0/admin``
115 * ``/_synapse/admin/v1``
116
117 The endpoints with ``/_matrix/client/*`` prefixes have been removed as of v1.24.0.
118 The Admin API is now only accessible under:
119
120 * ``/_synapse/admin/v1``
121
122 The only exception is the `/admin/whois` endpoint, which is
123 `also available via the client-server API <https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-admin-whois-userid>`_.
124
125 The deprecation of the old endpoints was announced with Synapse 1.20.0 (released
126 on 2020-09-22) and makes it easier for homeserver admins to lock down external
127 access to the Admin API endpoints.
76128
77129 Upgrading to v1.23.0
78130 ====================
1919 ```
2020
2121 ### for Prometheus v2
22
2223 Add a new job to the main prometheus.yml file:
2324
2425 ```yaml
2829 scheme: "https"
2930
3031 static_configs:
31 - targets: ['SERVER.LOCATION:PORT']
32 - targets: ["my.server.here:port"]
3233 ```
34
35 An example of a Prometheus configuration with workers can be found in
36 [metrics-howto.md](https://github.com/matrix-org/synapse/blob/master/docs/metrics-howto.md).
3337
3438 To use `synapse.rules` add
3539
3640 ```yaml
37 rule_files:
38 - "/PATH/TO/synapse-v2.rules"
41 rule_files:
42 - "/PATH/TO/synapse-v2.rules"
3943 ```
4044
4145 Metrics are disabled by default when running synapse; they must be enabled
88 new PromConsole.Graph({
99 node: document.querySelector("#process_resource_utime"),
1010 expr: "rate(process_cpu_seconds_total[2m]) * 100",
11 name: "[[job]]",
11 name: "[[job]]-[[index]]",
1212 min: 0,
1313 max: 100,
1414 renderer: "line",
2121 </script>
2222
2323 <h3>Memory</h3>
24 <div id="process_resource_maxrss"></div>
25 <script>
26 new PromConsole.Graph({
27 node: document.querySelector("#process_resource_maxrss"),
28 expr: "process_psutil_rss:max",
29 name: "Maxrss",
24 <div id="process_resident_memory_bytes"></div>
25 <script>
26 new PromConsole.Graph({
27 node: document.querySelector("#process_resident_memory_bytes"),
28 expr: "process_resident_memory_bytes",
29 name: "[[job]]-[[index]]",
3030 min: 0,
3131 renderer: "line",
3232 height: 150,
4242 <script>
4343 new PromConsole.Graph({
4444 node: document.querySelector("#process_fds"),
45 expr: "process_open_fds{job='synapse'}",
46 name: "FDs",
45 expr: "process_open_fds",
46 name: "[[job]]-[[index]]",
4747 min: 0,
4848 renderer: "line",
4949 height: 150,
6161 <script>
6262 new PromConsole.Graph({
6363 node: document.querySelector("#reactor_total_time"),
64 expr: "rate(python_twisted_reactor_tick_time:total[2m]) / 1000",
65 name: "time",
64 expr: "rate(python_twisted_reactor_tick_time_sum[2m])",
65 name: "[[job]]-[[index]]",
6666 max: 1,
6767 min: 0,
6868 renderer: "area",
7979 <script>
8080 new PromConsole.Graph({
8181 node: document.querySelector("#reactor_average_time"),
82 expr: "rate(python_twisted_reactor_tick_time:total[2m]) / rate(python_twisted_reactor_tick_time:count[2m]) / 1000",
83 name: "time",
82 expr: "rate(python_twisted_reactor_tick_time_sum[2m]) / rate(python_twisted_reactor_tick_time_count[2m])",
83 name: "[[job]]-[[index]]",
8484 min: 0,
8585 renderer: "line",
8686 height: 150,
9696 <script>
9797 new PromConsole.Graph({
9898 node: document.querySelector("#reactor_pending_calls"),
99 expr: "rate(python_twisted_reactor_pending_calls:total[30s])/rate(python_twisted_reactor_pending_calls:count[30s])",
100 name: "calls",
101 min: 0,
102 renderer: "line",
103 height: 150,
104 yAxisFormatter: PromConsole.NumberFormatter.humanize,
105 yHoverFormatter: PromConsole.NumberFormatter.humanize,
106 yTitle: "Pending Cals"
99 expr: "rate(python_twisted_reactor_pending_calls_sum[30s]) / rate(python_twisted_reactor_pending_calls_count[30s])",
100 name: "[[job]]-[[index]]",
101 min: 0,
102 renderer: "line",
103 height: 150,
104 yAxisFormatter: PromConsole.NumberFormatter.humanize,
105 yHoverFormatter: PromConsole.NumberFormatter.humanize,
106 yTitle: "Pending Calls"
107107 })
108108 </script>
109109
114114 <script>
115115 new PromConsole.Graph({
116116 node: document.querySelector("#synapse_storage_query_time"),
117 expr: "rate(synapse_storage_query_time:count[2m])",
117 expr: "sum(rate(synapse_storage_query_time_count[2m])) by (verb)",
118118 name: "[[verb]]",
119119 yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
120120 yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
128128 <script>
129129 new PromConsole.Graph({
130130 node: document.querySelector("#synapse_storage_transaction_time"),
131 expr: "rate(synapse_storage_transaction_time:count[2m])",
132 name: "[[desc]]",
131 expr: "topk(10, rate(synapse_storage_transaction_time_count[2m]))",
132 name: "[[job]]-[[index]] [[desc]]",
133133 min: 0,
134134 yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
135135 yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
139139 </script>
140140
141141 <h3>Transaction execution time</h3>
142 <div id="synapse_storage_transactions_time_msec"></div>
143 <script>
144 new PromConsole.Graph({
145 node: document.querySelector("#synapse_storage_transactions_time_msec"),
146 expr: "rate(synapse_storage_transaction_time:total[2m]) / 1000",
147 name: "[[desc]]",
142 <div id="synapse_storage_transactions_time_sec"></div>
143 <script>
144 new PromConsole.Graph({
145 node: document.querySelector("#synapse_storage_transactions_time_sec"),
146 expr: "rate(synapse_storage_transaction_time_sum[2m])",
147 name: "[[job]]-[[index]] [[desc]]",
148148 min: 0,
149149 yAxisFormatter: PromConsole.NumberFormatter.humanize,
150150 yHoverFormatter: PromConsole.NumberFormatter.humanize,
153153 })
154154 </script>
155155
156 <h3>Database scheduling latency</h3>
157 <div id="synapse_storage_schedule_time"></div>
158 <script>
159 new PromConsole.Graph({
160 node: document.querySelector("#synapse_storage_schedule_time"),
161 expr: "rate(synapse_storage_schedule_time:total[2m]) / 1000",
162 name: "Total latency",
163 min: 0,
164 yAxisFormatter: PromConsole.NumberFormatter.humanize,
165 yHoverFormatter: PromConsole.NumberFormatter.humanize,
166 yUnits: "s/s",
167 yTitle: "Usage"
168 })
169 </script>
170
171 <h3>Cache hit ratio</h3>
172 <div id="synapse_cache_ratio"></div>
173 <script>
174 new PromConsole.Graph({
175 node: document.querySelector("#synapse_cache_ratio"),
176 expr: "rate(synapse_util_caches_cache:total[2m]) * 100",
177 name: "[[name]]",
178 min: 0,
179 max: 100,
180 yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
181 yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
182 yUnits: "%",
183 yTitle: "Percentage"
156 <h3>Average time waiting for database connection</h3>
157 <div id="synapse_storage_avg_waiting_time"></div>
158 <script>
159 new PromConsole.Graph({
160 node: document.querySelector("#synapse_storage_avg_waiting_time"),
161 expr: "rate(synapse_storage_schedule_time_sum[2m]) / rate(synapse_storage_schedule_time_count[2m])",
162 name: "[[job]]-[[index]]",
163 min: 0,
164 yAxisFormatter: PromConsole.NumberFormatter.humanize,
165 yHoverFormatter: PromConsole.NumberFormatter.humanize,
166 yUnits: "s",
167 yTitle: "Time"
168 })
169 </script>
170
171 <h3>Cache request rate</h3>
172 <div id="synapse_cache_request_rate"></div>
173 <script>
174 new PromConsole.Graph({
175 node: document.querySelector("#synapse_cache_request_rate"),
176 expr: "rate(synapse_util_caches_cache:total[2m])",
177 name: "[[job]]-[[index]] [[name]]",
178 min: 0,
179 yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
180 yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
181 yUnits: "rps",
182 yTitle: "Cache request rate"
184183 })
185184 </script>
186185
190189 new PromConsole.Graph({
191190 node: document.querySelector("#synapse_cache_size"),
192191 expr: "synapse_util_caches_cache:size",
193 name: "[[name]]",
192 name: "[[job]]-[[index]] [[name]]",
194193 yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
195194 yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
196195 yUnits: "",
205204 <script>
206205 new PromConsole.Graph({
207206 node: document.querySelector("#synapse_http_server_request_count_servlet"),
208 expr: "rate(synapse_http_server_request_count:servlet[2m])",
209 name: "[[servlet]]",
207 expr: "rate(synapse_http_server_in_flight_requests_count[2m])",
208 name: "[[job]]-[[index]] [[method]] [[servlet]]",
210209 yAxisFormatter: PromConsole.NumberFormatter.humanize,
211210 yHoverFormatter: PromConsole.NumberFormatter.humanize,
212211 yUnits: "req/s",
218217 <script>
219218 new PromConsole.Graph({
220219 node: document.querySelector("#synapse_http_server_request_count_servlet_minus_events"),
221 expr: "rate(synapse_http_server_request_count:servlet{servlet!=\"EventStreamRestServlet\", servlet!=\"SyncRestServlet\"}[2m])",
222 name: "[[servlet]]",
220 expr: "rate(synapse_http_server_in_flight_requests_count{servlet!=\"EventStreamRestServlet\", servlet!=\"SyncRestServlet\"}[2m])",
221 name: "[[job]]-[[index]] [[method]] [[servlet]]",
223222 yAxisFormatter: PromConsole.NumberFormatter.humanize,
224223 yHoverFormatter: PromConsole.NumberFormatter.humanize,
225224 yUnits: "req/s",
232231 <script>
233232 new PromConsole.Graph({
234233 node: document.querySelector("#synapse_http_server_response_time_avg"),
235 expr: "rate(synapse_http_server_response_time_seconds[2m]) / rate(synapse_http_server_response_count[2m]) / 1000",
236 name: "[[servlet]]",
234 expr: "rate(synapse_http_server_response_time_seconds_sum[2m]) / rate(synapse_http_server_response_count[2m])",
235 name: "[[job]]-[[index]] [[servlet]]",
237236 yAxisFormatter: PromConsole.NumberFormatter.humanize,
238237 yHoverFormatter: PromConsole.NumberFormatter.humanize,
239238 yUnits: "s/req",
276275 new PromConsole.Graph({
277276 node: document.querySelector("#synapse_http_server_response_ru_utime"),
278277 expr: "rate(synapse_http_server_response_ru_utime_seconds[2m])",
279 name: "[[servlet]]",
278 name: "[[job]]-[[index]] [[servlet]]",
280279 yAxisFormatter: PromConsole.NumberFormatter.humanize,
281280 yHoverFormatter: PromConsole.NumberFormatter.humanize,
282281 yUnits: "s/s",
291290 new PromConsole.Graph({
292291 node: document.querySelector("#synapse_http_server_response_db_txn_duration"),
293292 expr: "rate(synapse_http_server_response_db_txn_duration_seconds[2m])",
294 name: "[[servlet]]",
293 name: "[[job]]-[[index]] [[servlet]]",
295294 yAxisFormatter: PromConsole.NumberFormatter.humanize,
296295 yHoverFormatter: PromConsole.NumberFormatter.humanize,
297296 yUnits: "s/s",
305304 <script>
306305 new PromConsole.Graph({
307306 node: document.querySelector("#synapse_http_server_send_time_avg"),
308 expr: "rate(synapse_http_server_response_time_second{servlet='RoomSendEventRestServlet'}[2m]) / rate(synapse_http_server_response_count{servlet='RoomSendEventRestServlet'}[2m]) / 1000",
309 name: "[[servlet]]",
307 expr: "rate(synapse_http_server_response_time_seconds_sum{servlet='RoomSendEventRestServlet'}[2m]) / rate(synapse_http_server_response_count{servlet='RoomSendEventRestServlet'}[2m])",
308 name: "[[job]]-[[index]] [[servlet]]",
310309 yAxisFormatter: PromConsole.NumberFormatter.humanize,
311310 yHoverFormatter: PromConsole.NumberFormatter.humanize,
312311 yUnits: "s/req",
322321 new PromConsole.Graph({
323322 node: document.querySelector("#synapse_federation_client_sent"),
324323 expr: "rate(synapse_federation_client_sent[2m])",
325 name: "[[type]]",
324 name: "[[job]]-[[index]] [[type]]",
326325 yAxisFormatter: PromConsole.NumberFormatter.humanize,
327326 yHoverFormatter: PromConsole.NumberFormatter.humanize,
328327 yUnits: "req/s",
336335 new PromConsole.Graph({
337336 node: document.querySelector("#synapse_federation_server_received"),
338337 expr: "rate(synapse_federation_server_received[2m])",
339 name: "[[type]]",
338 name: "[[job]]-[[index]] [[type]]",
340339 yAxisFormatter: PromConsole.NumberFormatter.humanize,
341340 yHoverFormatter: PromConsole.NumberFormatter.humanize,
342341 yUnits: "req/s",
366365 new PromConsole.Graph({
367366 node: document.querySelector("#synapse_notifier_listeners"),
368367 expr: "synapse_notifier_listeners",
369 name: "listeners",
368 name: "[[job]]-[[index]]",
370369 min: 0,
371370 yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
372371 yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
381380 new PromConsole.Graph({
382381 node: document.querySelector("#synapse_notifier_notified_events"),
383382 expr: "rate(synapse_notifier_notified_events[2m])",
384 name: "events",
383 name: "[[job]]-[[index]]",
385384 yAxisFormatter: PromConsole.NumberFormatter.humanize,
386385 yHoverFormatter: PromConsole.NumberFormatter.humanize,
387386 yUnits: "events/s",
0 matrix-synapse-py3 (1.24.0) stable; urgency=medium
1
2 * New synapse release 1.24.0.
3
4 -- Synapse Packaging team <packages@matrix.org> Wed, 09 Dec 2020 10:14:30 +0000
5
06 matrix-synapse-py3 (1.23.0) stable; urgency=medium
17
28 * New synapse release 1.23.0.
3636 jaeger-client \
3737 opentracing \
3838 # Match the version constraints of Synapse
39 "prometheus_client>=0.4.0,<0.9.0" \
39 "prometheus_client>=0.4.0" \
4040 psycopg2 \
4141 pycparser \
4242 pyrsistent \
00 # List all media in a room
11
22 This API gets a list of known media in a room.
3 However, it only shows media from unencrypted events or rooms.
34
45 The API is:
56 ```
381381
382382 The API is:
383383
384 ```json
384 ```
385385 POST /_synapse/admin/v1/rooms/<room_id>/delete
386386 ```
387387
438438 future attempts to join the room. Defaults to `false`.
439439 * `purge` - Optional. If set to `true`, it will remove all traces of the room from your database.
440440 Defaults to `true`.
441 * `force_purge` - Optional, and ignored unless `purge` is `true`. If set to `true`, it
442 will force a purge to go ahead even if there are local users still in the room. Do not
443 use this unless a regular `purge` operation fails, as it could leave those users'
444 clients in a confused state.
441445
442446 The JSON body must not be empty. The body must be at least `{}`.
443447
175175
176176 GET /_synapse/admin/v1/whois/<user_id>
177177
178 and::
179
180 GET /_matrix/client/r0/admin/whois/<userId>
181
182 See also: `Client Server API Whois
183 <https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-admin-whois-userid>`_
184
178185 To use it, you will need to authenticate by providing an ``access_token`` for a
179186 server admin: see `README.rst <README.rst>`_.
180187
253260
254261 {
255262 "new_password": "<secret>",
256 "logout_devices": true,
263 "logout_devices": true
257264 }
258265
259266 To use it, you will need to authenticate by providing an ``access_token`` for a
423430 - ``next_token``: integer - Indication for pagination. See above.
424431 - ``total`` - integer - Total number of media.
425432
433 Login as a user
434 ===============
435
436 Get an access token that can be used to authenticate as that user. Useful for
437 when admins wish to do actions on behalf of a user.
438
439 The API is::
440
441 POST /_synapse/admin/v1/users/<user_id>/login
442 {}
443
444 An optional ``valid_until_ms`` field can be specified in the request body as an
445 integer timestamp that specifies when the token should expire. By default tokens
446 do not expire.
447
448 A response body like the following is returned:
449
450 .. code:: json
451
452 {
453 "access_token": "<opaque_access_token_string>"
454 }
455
456
457 This API does *not* generate a new device for the user, and so will not appear
458 their ``/devices`` list, and in general the target user should not be able to
459 tell they have been logged in as.
460
461 To expire the token call the standard ``/logout`` API with the token.
462
463 Note: The token will expire if the *admin* user calls ``/logout/all`` from any
464 of their devices, but the token will *not* expire if the target user does the
465 same.
466
467
426468 User devices
427469 ============
428470
1212 can be enabled by adding the \"metrics\" resource to the existing
1313 listener as such:
1414
15 resources:
16 - names:
17 - client
18 - metrics
15 ```yaml
16 resources:
17 - names:
18 - client
19 - metrics
20 ```
1921
2022 This provides a simple way of adding metrics to your Synapse
2123 installation, and serves under `/_synapse/metrics`. If you do not
3032
3133 Add a new listener to homeserver.yaml:
3234
33 listeners:
34 - type: metrics
35 port: 9000
36 bind_addresses:
37 - '0.0.0.0'
35 ```yaml
36 listeners:
37 - type: metrics
38 port: 9000
39 bind_addresses:
40 - '0.0.0.0'
41 ```
3842
3943 For both options, you will need to ensure that `enable_metrics` is
4044 set to `True`.
4650 It needs to set the `metrics_path` to a non-default value (under
4751 `scrape_configs`):
4852
49 - job_name: "synapse"
50 metrics_path: "/_synapse/metrics"
51 static_configs:
52 - targets: ["my.server.here:port"]
53 ```yaml
54 - job_name: "synapse"
55 scrape_interval: 15s
56 metrics_path: "/_synapse/metrics"
57 static_configs:
58 - targets: ["my.server.here:port"]
59 ```
5360
5461 where `my.server.here` is the IP address of Synapse, and `port` is
5562 the listener port configured with the `metrics` resource.
5966
6067 1. Restart Prometheus.
6168
62 1. Consider using the [grafana dashboard](https://github.com/matrix-org/synapse/tree/master/contrib/grafana/) and required [recording rules](https://github.com/matrix-org/synapse/tree/master/contrib/prometheus/)
69 1. Consider using the [grafana dashboard](https://github.com/matrix-org/synapse/tree/master/contrib/grafana/)
70 and required [recording rules](https://github.com/matrix-org/synapse/tree/master/contrib/prometheus/)
6371
6472 ## Monitoring workers
6573
7583 under `worker_listeners`:
7684
7785 ```yaml
78 - type: metrics
79 bind_address: ''
80 port: 9101
86 - type: metrics
87 bind_address: ''
88 port: 9101
8189 ```
8290
8391 The `bind_address` and `port` parameters should be set so that
8593 don't clash with an existing worker.
8694 With this example, the worker's metrics would then be available
8795 on `http://127.0.0.1:9101`.
96
97 Example Prometheus target for Synapse with workers:
98
99 ```yaml
100 - job_name: "synapse"
101 scrape_interval: 15s
102 metrics_path: "/_synapse/metrics"
103 static_configs:
104 - targets: ["my.server.here:port"]
105 labels:
106 instance: "my.server"
107 job: "master"
108 index: 1
109 - targets: ["my.workerserver.here:port"]
110 labels:
111 instance: "my.server"
112 job: "generic_worker"
113 index: 1
114 - targets: ["my.workerserver.here:port"]
115 labels:
116 instance: "my.server"
117 job: "generic_worker"
118 index: 2
119 - targets: ["my.workerserver.here:port"]
120 labels:
121 instance: "my.server"
122 job: "media_repository"
123 index: 1
124 ```
125
126 Labels (`instance`, `job`, `index`) can be defined as anything.
127 The labels are used to group graphs in grafana.
88128
89129 ## Renaming of metrics & deprecation of old names in 1.2
90130
2525
2626 It should perform any appropriate sanity checks on the provided
2727 configuration, and return an object which is then passed into
28 `__init__`.
2829
2930 This method should have the `@staticmethod` decoration.
3031
12291229 # email will be globally disabled.
12301230 #
12311231 # Additionally, if `msisdn` is not set, registration and password resets via msisdn
1232 # will be disabled regardless. This is due to Synapse currently not supporting any
1233 # method of sending SMS messages on its own.
1232 # will be disabled regardless, and users will not be able to associate an msisdn
1233 # identifier to their account. This is due to Synapse currently not supporting
1234 # any method of sending SMS messages on its own.
12341235 #
12351236 # To enable using an identity server for operations regarding a particular third-party
12361237 # identifier type, set the value to the URL of that identity server as shown in the
15431544 # local: ["saml2/idp.xml"]
15441545 # remote:
15451546 # - url: https://our_idp/metadata.xml
1547
1548 # Allowed clock difference in seconds between the homeserver and IdP.
1549 #
1550 # Uncomment the below to increase the accepted time difference from 0 to 3 seconds.
1551 #
1552 #accepted_time_diff: 3
15461553
15471554 # By default, the user has to go to our login page first. If you'd like
15481555 # to allow IdP-initiated login, set 'allow_unsolicited: true' in a
16661673 # - attribute: department
16671674 # value: "sales"
16681675
1676 # If the metadata XML contains multiple IdP entities then the `idp_entityid`
1677 # option must be set to the entity to redirect users to.
1678 #
1679 # Most deployments only have a single IdP entity and so should omit this
1680 # option.
1681 #
1682 #idp_entityid: 'https://our_idp/entityid'
1683
16691684
16701685 # Enable OpenID Connect (OIDC) / OAuth 2.0 for registration and login.
16711686 #
22352250
22362251
22372252
2238 # Clients requesting push notifications can either have the body of
2239 # the message sent in the notification poke along with other details
2240 # like the sender, or just the event ID and room ID (`event_id_only`).
2241 # If clients choose the former, this option controls whether the
2242 # notification request includes the content of the event (other details
2243 # like the sender are still included). For `event_id_only` push, it
2244 # has no effect.
2245 #
2246 # For modern android devices the notification content will still appear
2247 # because it is loaded by the app. iPhone, however will send a
2248 # notification saying only that a message arrived and who it came from.
2249 #
2250 #push:
2251 # include_content: true
2253 ## Push ##
2254
2255 push:
2256 # Clients requesting push notifications can either have the body of
2257 # the message sent in the notification poke along with other details
2258 # like the sender, or just the event ID and room ID (`event_id_only`).
2259 # If clients choose the former, this option controls whether the
2260 # notification request includes the content of the event (other details
2261 # like the sender are still included). For `event_id_only` push, it
2262 # has no effect.
2263 #
2264 # For modern android devices the notification content will still appear
2265 # because it is loaded by the app. iPhone, however will send a
2266 # notification saying only that a message arrived and who it came from.
2267 #
2268 # The default value is "true" to include message details. Uncomment to only
2269 # include the event ID and room ID in push notification payloads.
2270 #
2271 #include_content: false
2272
2273 # When a push notification is received, an unread count is also sent.
2274 # This number can either be calculated as the number of unread messages
2275 # for the user, or the number of *rooms* the user has unread messages in.
2276 #
2277 # The default value is "true", meaning push clients will see the number of
2278 # rooms with unread messages in them. Uncomment to instead send the number
2279 # of unread messages.
2280 #
2281 #group_unread_count_by_room: false
22522282
22532283
22542284 # Spam checkers are third-party modules that can block specific actions
1414 SSO mapping providers are currently supported for OpenID and SAML SSO
1515 configurations. Please see the details below for how to implement your own.
1616
17 It is the responsibility of the mapping provider to normalise the SSO attributes
18 and map them to a valid Matrix ID. The
19 [specification for Matrix IDs](https://matrix.org/docs/spec/appendices#user-identifiers)
20 has some information about what is considered valid. Alternately an easy way to
21 ensure it is valid is to use a Synapse utility function:
22 `synapse.types.map_username_to_mxid_localpart`.
23
1724 External mapping providers are provided to Synapse in the form of an external
18 Python module. You can retrieve this module from [PyPi](https://pypi.org) or elsewhere,
25 Python module. You can retrieve this module from [PyPI](https://pypi.org) or elsewhere,
1926 but it must be importable via Synapse (e.g. it must be in the same virtualenv
2027 as Synapse). The Synapse config is then modified to point to the mapping provider
2128 (and optionally provide additional configuration for it).
5562 information from.
5663 - This method must return a string, which is the unique identifier for the
5764 user. Commonly the ``sub`` claim of the response.
58 * `map_user_attributes(self, userinfo, token)`
65 * `map_user_attributes(self, userinfo, token, failures)`
5966 - This method must be async.
6067 - Arguments:
6168 - `userinfo` - A `authlib.oidc.core.claims.UserInfo` object to extract user
6269 information from.
6370 - `token` - A dictionary which includes information necessary to make
6471 further requests to the OpenID provider.
72 - `failures` - An `int` that represents the amount of times the returned
73 mxid localpart mapping has failed. This should be used
74 to create a deduplicated mxid localpart which should be
75 returned instead. For example, if this method returns
76 `john.doe` as the value of `localpart` in the returned
77 dict, and that is already taken on the homeserver, this
78 method will be called again with the same parameters but
79 with failures=1. The method should then return a different
80 `localpart` value, such as `john.doe1`.
6581 - Returns a dictionary with two keys:
6682 - localpart: A required string, used to generate the Matrix ID.
6783 - displayname: An optional string, the display name for the user.
151167 the value of `mxid_localpart`.
152168 * `emails` - A list of emails for the new user. If not provided, will
153169 default to an empty list.
170
171 Alternatively it can raise a `synapse.api.errors.RedirectException` to
172 redirect the user to another page. This is useful to prompt the user for
173 additional information, e.g. if you want them to provide their own username.
174 It is the responsibility of the mapping provider to either redirect back
175 to `client_redirect_url` (including any additional information) or to
176 complete registration using methods from the `ModuleApi`.
154177
155178 ### Default SAML Mapping Provider
156179
4141
4242 ./configure
4343
44 > You may need to install `libevent2`: if so, you should do so in
45 > the way recommended by your operating system. You can ignore
46 > warnings about lack of database support: a database is unnecessary
47 > for this purpose.
44 You may need to install `libevent2`: if so, you should do so in
45 the way recommended by your operating system. You can ignore
46 warnings about lack of database support: a database is unnecessary
47 for this purpose.
4848
4949 1. Build and install it:
5050
6464 the `static-auth-secret` is with `pwgen`:
6565
6666 pwgen -s 64 1
67
68 A `realm` must be specified, but its value is somewhat arbitrary. (It is
69 sent to clients as part of the authentication flow.) It is conventional to
70 set it to be your server name.
71
72 1. You will most likely want to configure coturn to write logs somewhere. The
73 easiest way is normally to send them to the syslog:
74
75 syslog
76
77 (in which case, the logs will be available via `journalctl -u coturn` on a
78 systemd system). Alternatively, coturn can be configured to write to a
79 logfile - check the example config file supplied with coturn.
6780
6881 1. Consider your security settings. TURN lets users request a relay which will
6982 connect to arbitrary IP addresses and ports. The following configuration is
95108 # TLS private key file
96109 pkey=/path/to/privkey.pem
97110
111 In this case, replace the `turn:` schemes in the `turn_uri` settings below
112 with `turns:`.
113
114 We recommend that you only try to set up TLS/DTLS once you have set up a
115 basic installation and got it working.
116
98117 1. Ensure your firewall allows traffic into the TURN server on the ports
99 you've configured it to listen on (By default: 3478 and 5349 for the TURN(s)
118 you've configured it to listen on (By default: 3478 and 5349 for TURN
100119 traffic (remember to allow both TCP and UDP traffic), and ports 49152-65535
101120 for the UDP relay.)
121
122 1. We do not recommend running a TURN server behind NAT, and are not aware of
123 anyone doing so successfully.
124
125 If you want to try it anyway, you will at least need to tell coturn its
126 external IP address:
127
128 external-ip=192.88.99.1
129
130 ... and your NAT gateway must forward all of the relayed ports directly
131 (eg, port 56789 on the external IP must be always be forwarded to port
132 56789 on the internal IP).
133
134 If you get this working, let us know!
102135
103136 1. (Re)start the turn server:
104137
136169 without having gone through a CAPTCHA or similar to register a
137170 real account.
138171
139 As an example, here is the relevant section of the config file for matrix.org:
140
141 turn_uris: [ "turn:turn.matrix.org:3478?transport=udp", "turn:turn.matrix.org:3478?transport=tcp" ]
172 As an example, here is the relevant section of the config file for `matrix.org`. The
173 `turn_uris` are appropriate for TURN servers listening on the default ports, with no TLS.
174
175 turn_uris: [ "turn:turn.matrix.org?transport=udp", "turn:turn.matrix.org?transport=tcp" ]
142176 turn_shared_secret: "n0t4ctuAllymatr1Xd0TorgSshar3d5ecret4obvIousreAsons"
143177 turn_user_lifetime: 86400000
144178 turn_allow_guests: True
154188 ```
155189 systemctl restart synapse.service
156190 ```
157
158 ..and your Home Server now supports VoIP relaying!
191 ... and then reload any clients (or wait an hour for them to refresh their
192 settings).
193
194 ## Troubleshooting
195
196 The normal symptoms of a misconfigured TURN server are that calls between
197 devices on different networks ring, but get stuck at "call
198 connecting". Unfortunately, troubleshooting this can be tricky.
199
200 Here are a few things to try:
201
202 * Check that your TURN server is not behind NAT. As above, we're not aware of
203 anyone who has successfully set this up.
204
205 * Check that you have opened your firewall to allow TCP and UDP traffic to the
206 TURN ports (normally 3478 and 5479).
207
208 * Check that you have opened your firewall to allow UDP traffic to the UDP
209 relay ports (49152-65535 by default).
210
211 * Some WebRTC implementations (notably, that of Google Chrome) appear to get
212 confused by TURN servers which are reachable over IPv6 (this appears to be
213 an unexpected side-effect of its handling of multiple IP addresses as
214 defined by
215 [`draft-ietf-rtcweb-ip-handling`](https://tools.ietf.org/html/draft-ietf-rtcweb-ip-handling-12)).
216
217 Try removing any AAAA records for your TURN server, so that it is only
218 reachable over IPv4.
219
220 * Enable more verbose logging in coturn via the `verbose` setting:
221
222 ```
223 verbose
224 ```
225
226 ... and then see if there are any clues in its logs.
227
228 * If you are using a browser-based client under Chrome, check
229 `chrome://webrtc-internals/` for insights into the internals of the
230 negotiation. On Firefox, check the "Connection Log" on `about:webrtc`.
231
232 (Understanding the output is beyond the scope of this document!)
233
234 * There is a WebRTC test tool at
235 https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/. To
236 use it, you will need a username/password for your TURN server. You can
237 either:
238
239 * look for the `GET /_matrix/client/r0/voip/turnServer` request made by a
240 matrix client to your homeserver in your browser's network inspector. In
241 the response you should see `username` and `password`. Or:
242
243 * Use the following shell commands:
244
245 ```sh
246 secret=staticAuthSecretHere
247
248 u=$((`date +%s` + 3600)):test
249 p=$(echo -n $u | openssl dgst -hmac $secret -sha1 -binary | base64)
250 echo -e "username: $u\npassword: $p"
251 ```
252
253 Or:
254
255 * Temporarily configure coturn to accept a static username/password. To do
256 this, comment out `use-auth-secret` and `static-auth-secret` and add the
257 following:
258
259 ```
260 lt-cred-mech
261 user=username:password
262 ```
263
264 **Note**: these settings will not take effect unless `use-auth-secret`
265 and `static-auth-secret` are disabled.
266
267 Restart coturn after changing the configuration file.
268
269 Remember to restore the original settings to go back to testing with
270 Matrix clients!
271
272 If the TURN server is working correctly, you should see at least one `relay`
273 entry in the results.
77 mypy_path = stubs
88 warn_unreachable = True
99 files =
10 scripts-dev/sign_json,
1011 synapse/api,
1112 synapse/appservice,
1213 synapse/config,
3637 synapse/handlers/presence.py,
3738 synapse/handlers/profile.py,
3839 synapse/handlers/read_marker.py,
40 synapse/handlers/register.py,
3941 synapse/handlers/room.py,
4042 synapse/handlers/room_member.py,
4143 synapse/handlers/room_member_worker.py,
4244 synapse/handlers/saml_handler.py,
4345 synapse/handlers/sync.py,
4446 synapse/handlers/ui_auth,
47 synapse/http/client.py,
48 synapse/http/federation/matrix_federation_agent.py,
4549 synapse/http/federation/well_known_resolver.py,
50 synapse/http/matrixfederationclient.py,
4651 synapse/http/server.py,
4752 synapse/http/site.py,
4853 synapse/logging,
7479 synapse/util/metrics.py,
7580 tests/replication,
7681 tests/test_utils,
82 tests/handlers/test_password_providers.py,
7783 tests/rest/client/v2_alpha/test_auth.py,
7884 tests/util/test_stream_change_cache.py
7985
104110 [mypy-opentracing]
105111 ignore_missing_imports = True
106112
107 [mypy-OpenSSL]
113 [mypy-OpenSSL.*]
108114 ignore_missing_imports = True
109115
110116 [mypy-netaddr]
0 #!/usr/bin/env python
1 #
2 # -*- coding: utf-8 -*-
3 # Copyright 2020 The Matrix.org Foundation C.I.C.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 import argparse
17 import json
18 import sys
19 from json import JSONDecodeError
20
21 import yaml
22 from signedjson.key import read_signing_keys
23 from signedjson.sign import sign_json
24
25 from synapse.util import json_encoder
26
27
28 def main():
29 parser = argparse.ArgumentParser(
30 description="""Adds a signature to a JSON object.
31
32 Example usage:
33
34 $ scripts-dev/sign_json.py -N test -k localhost.signing.key "{}"
35 {"signatures":{"test":{"ed25519:a_ZnZh":"LmPnml6iM0iR..."}}}
36 """,
37 formatter_class=argparse.RawDescriptionHelpFormatter,
38 )
39
40 parser.add_argument(
41 "-N",
42 "--server-name",
43 help="Name to give as the local homeserver. If unspecified, will be "
44 "read from the config file.",
45 )
46
47 parser.add_argument(
48 "-k",
49 "--signing-key-path",
50 help="Path to the file containing the private ed25519 key to sign the "
51 "request with.",
52 )
53
54 parser.add_argument(
55 "-c",
56 "--config",
57 default="homeserver.yaml",
58 help=(
59 "Path to synapse config file, from which the server name and/or signing "
60 "key path will be read. Ignored if --server-name and --signing-key-path "
61 "are both given."
62 ),
63 )
64
65 input_args = parser.add_mutually_exclusive_group()
66
67 input_args.add_argument("input_data", nargs="?", help="Raw JSON to be signed.")
68
69 input_args.add_argument(
70 "-i",
71 "--input",
72 type=argparse.FileType("r"),
73 default=sys.stdin,
74 help=(
75 "A file from which to read the JSON to be signed. If neither --input nor "
76 "input_data are given, JSON will be read from stdin."
77 ),
78 )
79
80 parser.add_argument(
81 "-o",
82 "--output",
83 type=argparse.FileType("w"),
84 default=sys.stdout,
85 help="Where to write the signed JSON. Defaults to stdout.",
86 )
87
88 args = parser.parse_args()
89
90 if not args.server_name or not args.signing_key_path:
91 read_args_from_config(args)
92
93 with open(args.signing_key_path) as f:
94 key = read_signing_keys(f)[0]
95
96 json_to_sign = args.input_data
97 if json_to_sign is None:
98 json_to_sign = args.input.read()
99
100 try:
101 obj = json.loads(json_to_sign)
102 except JSONDecodeError as e:
103 print("Unable to parse input as JSON: %s" % e, file=sys.stderr)
104 sys.exit(1)
105
106 if not isinstance(obj, dict):
107 print("Input json was not an object", file=sys.stderr)
108 sys.exit(1)
109
110 sign_json(obj, args.server_name, key)
111 for c in json_encoder.iterencode(obj):
112 args.output.write(c)
113 args.output.write("\n")
114
115
116 def read_args_from_config(args: argparse.Namespace) -> None:
117 with open(args.config, "r") as fh:
118 config = yaml.safe_load(fh)
119 if not args.server_name:
120 args.server_name = config["server_name"]
121 if not args.signing_key_path:
122 args.signing_key_path = config["signing_key_path"]
123
124
125 if __name__ == "__main__":
126 main()
4747 except ImportError:
4848 pass
4949
50 __version__ = "1.23.0"
50 __version__ = "1.24.0"
5151
5252 if bool(os.environ.get("SYNAPSE_TEST_PATCH_LOG_CONTEXTS", False)):
5353 # We import here so that we don't have to install a bunch of deps when
3636 exit=sys.exit,
3737 ):
3838
39 url = "%s/_matrix/client/r0/admin/register" % (server_location,)
39 url = "%s/_synapse/admin/v1/register" % (server_location.rstrip("/"),)
4040
4141 # Get the nonce
4242 r = requests.get(url, verify=False)
1313 # limitations under the License.
1414
1515 import logging
16 from typing import Optional
1617
1718 from synapse.api.constants import LimitBlockingTypes, UserTypes
1819 from synapse.api.errors import Codes, ResourceLimitError
1920 from synapse.config.server import is_threepid_reserved
21 from synapse.types import Requester
2022
2123 logger = logging.getLogger(__name__)
2224
3234 self._max_mau_value = hs.config.max_mau_value
3335 self._limit_usage_by_mau = hs.config.limit_usage_by_mau
3436 self._mau_limits_reserved_threepids = hs.config.mau_limits_reserved_threepids
37 self._server_name = hs.hostname
3538
36 async def check_auth_blocking(self, user_id=None, threepid=None, user_type=None):
39 async def check_auth_blocking(
40 self,
41 user_id: Optional[str] = None,
42 threepid: Optional[dict] = None,
43 user_type: Optional[str] = None,
44 requester: Optional[Requester] = None,
45 ):
3746 """Checks if the user should be rejected for some external reason,
3847 such as monthly active user limiting or global disable flag
3948
4049 Args:
41 user_id(str|None): If present, checks for presence against existing
50 user_id: If present, checks for presence against existing
4251 MAU cohort
4352
44 threepid(dict|None): If present, checks for presence against configured
53 threepid: If present, checks for presence against configured
4554 reserved threepid. Used in cases where the user is trying register
4655 with a MAU blocked server, normally they would be rejected but their
4756 threepid is on the reserved list. user_id and
4857 threepid should never be set at the same time.
4958
50 user_type(str|None): If present, is used to decide whether to check against
59 user_type: If present, is used to decide whether to check against
5160 certain blocking reasons like MAU.
61
62 requester: If present, and the authenticated entity is a user, checks for
63 presence against existing MAU cohort. Passing in both a `user_id` and
64 `requester` is an error.
5265 """
66 if requester and user_id:
67 raise Exception(
68 "Passed in both 'user_id' and 'requester' to 'check_auth_blocking'"
69 )
70
71 if requester:
72 if requester.authenticated_entity.startswith("@"):
73 user_id = requester.authenticated_entity
74 elif requester.authenticated_entity == self._server_name:
75 # We never block the server from doing actions on behalf of
76 # users.
77 return
5378
5479 # Never fail an auth check for the server notices users or support user
5580 # This can be a problem where event creation is prohibited due to blocking
3131 from synapse.config.server import ListenerConfig
3232 from synapse.crypto import context_factory
3333 from synapse.logging.context import PreserveLoggingContext
34 from synapse.metrics.background_process_metrics import wrap_as_background_process
3435 from synapse.util.async_helpers import Linearizer
3536 from synapse.util.daemonize import daemonize_process
3637 from synapse.util.rlimit import change_resource_limit
243244 # Set up the SIGHUP machinery.
244245 if hasattr(signal, "SIGHUP"):
245246
247 @wrap_as_background_process("sighup")
246248 def handle_sighup(*args, **kwargs):
247249 # Tell systemd our state, if we're using it. This will silently fail if
248250 # we're not using systemd.
253255
254256 sdnotify(b"READY=1")
255257
256 signal.signal(signal.SIGHUP, handle_sighup)
258 # We defer running the sighup handlers until next reactor tick. This
259 # is so that we're in a sane state, e.g. flushing the logs may fail
260 # if the sighup happens in the middle of writing a log entry.
261 def run_sighup(*args, **kwargs):
262 hs.get_clock().call_later(0, handle_sighup, *args, **kwargs)
263
264 signal.signal(signal.SIGHUP, run_sighup)
257265
258266 register_sighup(refresh_certificate, hs)
259267
2020 section = "push"
2121
2222 def read_config(self, config, **kwargs):
23 push_config = config.get("push", {})
23 push_config = config.get("push") or {}
2424 self.push_include_content = push_config.get("include_content", True)
25 self.push_group_unread_count_by_room = push_config.get(
26 "group_unread_count_by_room", True
27 )
2528
2629 pusher_instances = config.get("pusher_instances") or []
2730 self.pusher_shard_config = ShardedWorkerHandlingConfig(pusher_instances)
4851
4952 def generate_config_section(self, config_dir_path, server_name, **kwargs):
5053 return """
51 # Clients requesting push notifications can either have the body of
52 # the message sent in the notification poke along with other details
53 # like the sender, or just the event ID and room ID (`event_id_only`).
54 # If clients choose the former, this option controls whether the
55 # notification request includes the content of the event (other details
56 # like the sender are still included). For `event_id_only` push, it
57 # has no effect.
58 #
59 # For modern android devices the notification content will still appear
60 # because it is loaded by the app. iPhone, however will send a
61 # notification saying only that a message arrived and who it came from.
62 #
63 #push:
64 # include_content: true
54 ## Push ##
55
56 push:
57 # Clients requesting push notifications can either have the body of
58 # the message sent in the notification poke along with other details
59 # like the sender, or just the event ID and room ID (`event_id_only`).
60 # If clients choose the former, this option controls whether the
61 # notification request includes the content of the event (other details
62 # like the sender are still included). For `event_id_only` push, it
63 # has no effect.
64 #
65 # For modern android devices the notification content will still appear
66 # because it is loaded by the app. iPhone, however will send a
67 # notification saying only that a message arrived and who it came from.
68 #
69 # The default value is "true" to include message details. Uncomment to only
70 # include the event ID and room ID in push notification payloads.
71 #
72 #include_content: false
73
74 # When a push notification is received, an unread count is also sent.
75 # This number can either be calculated as the number of unread messages
76 # for the user, or the number of *rooms* the user has unread messages in.
77 #
78 # The default value is "true", meaning push clients will see the number of
79 # rooms with unread messages in them. Uncomment to instead send the number
80 # of unread messages.
81 #
82 #group_unread_count_by_room: false
6583 """
346346 # email will be globally disabled.
347347 #
348348 # Additionally, if `msisdn` is not set, registration and password resets via msisdn
349 # will be disabled regardless. This is due to Synapse currently not supporting any
350 # method of sending SMS messages on its own.
349 # will be disabled regardless, and users will not be able to associate an msisdn
350 # identifier to their account. This is due to Synapse currently not supporting
351 # any method of sending SMS messages on its own.
351352 #
352353 # To enable using an identity server for operations regarding a particular third-party
353354 # identifier type, set the value to the URL of that identity server as shown in the
8888 self.saml2_grandfathered_mxid_source_attribute = saml2_config.get(
8989 "grandfathered_mxid_source_attribute", "uid"
9090 )
91
92 self.saml2_idp_entityid = saml2_config.get("idp_entityid", None)
9193
9294 # user_mapping_provider may be None if the key is present but has no value
9395 ump_dict = saml2_config.get("user_mapping_provider") or {}
255257 # remote:
256258 # - url: https://our_idp/metadata.xml
257259
260 # Allowed clock difference in seconds between the homeserver and IdP.
261 #
262 # Uncomment the below to increase the accepted time difference from 0 to 3 seconds.
263 #
264 #accepted_time_diff: 3
265
258266 # By default, the user has to go to our login page first. If you'd like
259267 # to allow IdP-initiated login, set 'allow_unsolicited: true' in a
260268 # 'service.sp' section:
376384 # value: "staff"
377385 # - attribute: department
378386 # value: "sales"
387
388 # If the metadata XML contains multiple IdP entities then the `idp_entityid`
389 # option must be set to the entity to redirect users to.
390 #
391 # Most deployments only have a single IdP entity and so should omit this
392 # option.
393 #
394 #idp_entityid: 'https://our_idp/entityid'
379395 """ % {
380396 "config_dir_path": config_dir_path
381397 }
1414 # limitations under the License.
1515
1616 import inspect
17 from typing import Any, Dict, List, Optional, Tuple
17 from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
1818
1919 from synapse.spam_checker_api import RegistrationBehaviour
2020 from synapse.types import Collection
2121
22 MYPY = False
23 if MYPY:
22 if TYPE_CHECKING:
2423 import synapse.events
2524 import synapse.server
2625
4848 from synapse.federation.persistence import TransactionActions
4949 from synapse.federation.units import Edu, Transaction
5050 from synapse.http.endpoint import parse_server_name
51 from synapse.http.servlet import assert_params_in_dict
5152 from synapse.logging.context import (
5253 make_deferred_yieldable,
5354 nested_logging_context,
390391 TRANSACTION_CONCURRENCY_LIMIT,
391392 )
392393
393 async def on_context_state_request(
394 async def on_room_state_request(
394395 self, origin: str, room_id: str, event_id: str
395396 ) -> Tuple[int, Dict[str, Any]]:
396397 origin_host, _ = parse_server_name(origin)
513514 return {"event": ret_pdu.get_pdu_json(time_now)}
514515
515516 async def on_send_join_request(
516 self, origin: str, content: JsonDict, room_id: str
517 self, origin: str, content: JsonDict
517518 ) -> Dict[str, Any]:
518519 logger.debug("on_send_join_request: content: %s", content)
519520
520 room_version = await self.store.get_room_version(room_id)
521 assert_params_in_dict(content, ["room_id"])
522 room_version = await self.store.get_room_version(content["room_id"])
521523 pdu = event_from_pdu_json(content, room_version)
522524
523525 origin_host, _ = parse_server_name(origin)
546548 time_now = self._clock.time_msec()
547549 return {"event": pdu.get_pdu_json(time_now), "room_version": room_version}
548550
549 async def on_send_leave_request(
550 self, origin: str, content: JsonDict, room_id: str
551 ) -> dict:
551 async def on_send_leave_request(self, origin: str, content: JsonDict) -> dict:
552552 logger.debug("on_send_leave_request: content: %s", content)
553553
554 room_version = await self.store.get_room_version(room_id)
554 assert_params_in_dict(content, ["room_id"])
555 room_version = await self.store.get_room_version(content["room_id"])
555556 pdu = event_from_pdu_json(content, room_version)
556557
557558 origin_host, _ = parse_server_name(origin)
747748 )
748749 return ret
749750
750 async def on_exchange_third_party_invite_request(
751 self, room_id: str, event_dict: Dict
752 ):
753 ret = await self.handler.on_exchange_third_party_invite_request(
754 room_id, event_dict
755 )
751 async def on_exchange_third_party_invite_request(self, event_dict: Dict):
752 ret = await self.handler.on_exchange_third_party_invite_request(event_dict)
756753 return ret
757754
758755 async def check_server_matches_acl(self, server_name: str, room_id: str):
439439
440440
441441 class FederationStateV1Servlet(BaseFederationServlet):
442 PATH = "/state/(?P<context>[^/]*)/?"
443
444 # This is when someone asks for all data for a given context.
445 async def on_GET(self, origin, content, query, context):
446 return await self.handler.on_context_state_request(
442 PATH = "/state/(?P<room_id>[^/]*)/?"
443
444 # This is when someone asks for all data for a given room.
445 async def on_GET(self, origin, content, query, room_id):
446 return await self.handler.on_room_state_request(
447447 origin,
448 context,
448 room_id,
449449 parse_string_from_args(query, "event_id", None, required=False),
450450 )
451451
462462
463463
464464 class FederationBackfillServlet(BaseFederationServlet):
465 PATH = "/backfill/(?P<context>[^/]*)/?"
466
467 async def on_GET(self, origin, content, query, context):
465 PATH = "/backfill/(?P<room_id>[^/]*)/?"
466
467 async def on_GET(self, origin, content, query, room_id):
468468 versions = [x.decode("ascii") for x in query[b"v"]]
469469 limit = parse_integer_from_args(query, "limit", None)
470470
471471 if not limit:
472472 return 400, {"error": "Did not include limit param"}
473473
474 return await self.handler.on_backfill_request(origin, context, versions, limit)
474 return await self.handler.on_backfill_request(origin, room_id, versions, limit)
475475
476476
477477 class FederationQueryServlet(BaseFederationServlet):
486486
487487
488488 class FederationMakeJoinServlet(BaseFederationServlet):
489 PATH = "/make_join/(?P<context>[^/]*)/(?P<user_id>[^/]*)"
490
491 async def on_GET(self, origin, _content, query, context, user_id):
489 PATH = "/make_join/(?P<room_id>[^/]*)/(?P<user_id>[^/]*)"
490
491 async def on_GET(self, origin, _content, query, room_id, user_id):
492492 """
493493 Args:
494494 origin (unicode): The authenticated server_name of the calling server
510510 supported_versions = ["1"]
511511
512512 content = await self.handler.on_make_join_request(
513 origin, context, user_id, supported_versions=supported_versions
513 origin, room_id, user_id, supported_versions=supported_versions
514514 )
515515 return 200, content
516516
517517
518518 class FederationMakeLeaveServlet(BaseFederationServlet):
519 PATH = "/make_leave/(?P<context>[^/]*)/(?P<user_id>[^/]*)"
520
521 async def on_GET(self, origin, content, query, context, user_id):
522 content = await self.handler.on_make_leave_request(origin, context, user_id)
519 PATH = "/make_leave/(?P<room_id>[^/]*)/(?P<user_id>[^/]*)"
520
521 async def on_GET(self, origin, content, query, room_id, user_id):
522 content = await self.handler.on_make_leave_request(origin, room_id, user_id)
523523 return 200, content
524524
525525
527527 PATH = "/send_leave/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
528528
529529 async def on_PUT(self, origin, content, query, room_id, event_id):
530 content = await self.handler.on_send_leave_request(origin, content, room_id)
530 content = await self.handler.on_send_leave_request(origin, content)
531531 return 200, (200, content)
532532
533533
537537 PREFIX = FEDERATION_V2_PREFIX
538538
539539 async def on_PUT(self, origin, content, query, room_id, event_id):
540 content = await self.handler.on_send_leave_request(origin, content, room_id)
540 content = await self.handler.on_send_leave_request(origin, content)
541541 return 200, content
542542
543543
544544 class FederationEventAuthServlet(BaseFederationServlet):
545 PATH = "/event_auth/(?P<context>[^/]*)/(?P<event_id>[^/]*)"
546
547 async def on_GET(self, origin, content, query, context, event_id):
548 return await self.handler.on_event_auth(origin, context, event_id)
545 PATH = "/event_auth/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
546
547 async def on_GET(self, origin, content, query, room_id, event_id):
548 return await self.handler.on_event_auth(origin, room_id, event_id)
549549
550550
551551 class FederationV1SendJoinServlet(BaseFederationServlet):
552 PATH = "/send_join/(?P<context>[^/]*)/(?P<event_id>[^/]*)"
553
554 async def on_PUT(self, origin, content, query, context, event_id):
555 # TODO(paul): assert that context/event_id parsed from path actually
552 PATH = "/send_join/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
553
554 async def on_PUT(self, origin, content, query, room_id, event_id):
555 # TODO(paul): assert that room_id/event_id parsed from path actually
556556 # match those given in content
557 content = await self.handler.on_send_join_request(origin, content, context)
557 content = await self.handler.on_send_join_request(origin, content)
558558 return 200, (200, content)
559559
560560
561561 class FederationV2SendJoinServlet(BaseFederationServlet):
562 PATH = "/send_join/(?P<context>[^/]*)/(?P<event_id>[^/]*)"
562 PATH = "/send_join/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
563563
564564 PREFIX = FEDERATION_V2_PREFIX
565565
566 async def on_PUT(self, origin, content, query, context, event_id):
567 # TODO(paul): assert that context/event_id parsed from path actually
566 async def on_PUT(self, origin, content, query, room_id, event_id):
567 # TODO(paul): assert that room_id/event_id parsed from path actually
568568 # match those given in content
569 content = await self.handler.on_send_join_request(origin, content, context)
569 content = await self.handler.on_send_join_request(origin, content)
570570 return 200, content
571571
572572
573573 class FederationV1InviteServlet(BaseFederationServlet):
574 PATH = "/invite/(?P<context>[^/]*)/(?P<event_id>[^/]*)"
575
576 async def on_PUT(self, origin, content, query, context, event_id):
574 PATH = "/invite/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
575
576 async def on_PUT(self, origin, content, query, room_id, event_id):
577577 # We don't get a room version, so we have to assume its EITHER v1 or
578578 # v2. This is "fine" as the only difference between V1 and V2 is the
579579 # state resolution algorithm, and we don't use that for processing
588588
589589
590590 class FederationV2InviteServlet(BaseFederationServlet):
591 PATH = "/invite/(?P<context>[^/]*)/(?P<event_id>[^/]*)"
591 PATH = "/invite/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
592592
593593 PREFIX = FEDERATION_V2_PREFIX
594594
595 async def on_PUT(self, origin, content, query, context, event_id):
596 # TODO(paul): assert that context/event_id parsed from path actually
595 async def on_PUT(self, origin, content, query, room_id, event_id):
596 # TODO(paul): assert that room_id/event_id parsed from path actually
597597 # match those given in content
598598
599599 room_version = content["room_version"]
615615 PATH = "/exchange_third_party_invite/(?P<room_id>[^/]*)"
616616
617617 async def on_PUT(self, origin, content, query, room_id):
618 content = await self.handler.on_exchange_third_party_invite_request(
619 room_id, content
620 )
618 content = await self.handler.on_exchange_third_party_invite_request(content)
621619 return 200, content
622620
623621
168168 # and having homeservers have their own users leave keeps more
169169 # of that decision-making and control local to the guest-having
170170 # homeserver.
171 requester = synapse.types.create_requester(target_user, is_guest=True)
171 requester = synapse.types.create_requester(
172 target_user, is_guest=True, authenticated_entity=self.server_name
173 )
172174 handler = self.hs.get_room_member_handler()
173175 await handler.update_membership(
174176 requester,
225225 new_token: Optional[int],
226226 users: Collection[Union[str, UserID]],
227227 ):
228 logger.info("Checking interested services for %s" % (stream_key))
228 logger.debug("Checking interested services for %s" % (stream_key))
229229 with Measure(self.clock, "notify_interested_services_ephemeral"):
230230 for service in services:
231231 # Only handle typing if we have the latest token
00 # -*- coding: utf-8 -*-
11 # Copyright 2014 - 2016 OpenMarket Ltd
22 # Copyright 2017 Vector Creations Ltd
3 # Copyright 2019 - 2020 The Matrix.org Foundation C.I.C.
34 #
45 # Licensed under the Apache License, Version 2.0 (the "License");
56 # you may not use this file except in compliance with the License.
2425 Dict,
2526 Iterable,
2627 List,
28 Mapping,
2729 Optional,
2830 Tuple,
2931 Union,
180182 # better way to break the loop
181183 account_handler = ModuleApi(hs, self)
182184
183 self.password_providers = []
184 for module, config in hs.config.password_providers:
185 try:
186 self.password_providers.append(
187 module(config=config, account_handler=account_handler)
188 )
189 except Exception as e:
190 logger.error("Error while initializing %r: %s", module, e)
191 raise
192
193 logger.info("Extra password_providers: %r", self.password_providers)
185 self.password_providers = [
186 PasswordProvider.load(module, config, account_handler)
187 for module, config in hs.config.password_providers
188 ]
189
190 logger.info("Extra password_providers: %s", self.password_providers)
194191
195192 self.hs = hs # FIXME better possibility to access registrationHandler later?
196193 self.macaroon_gen = hs.get_macaroon_generator()
204201 # type in the list. (NB that the spec doesn't require us to do so and
205202 # clients which favour types that they don't understand over those that
206203 # they do are technically broken)
204
205 # start out by assuming PASSWORD is enabled; we will remove it later if not.
207206 login_types = []
208 if self._password_enabled:
207 if hs.config.password_localdb_enabled:
209208 login_types.append(LoginType.PASSWORD)
209
210210 for provider in self.password_providers:
211211 if hasattr(provider, "get_supported_login_types"):
212212 for t in provider.get_supported_login_types().keys():
213213 if t not in login_types:
214214 login_types.append(t)
215
216 if not self._password_enabled:
217 login_types.remove(LoginType.PASSWORD)
218
215219 self._supported_login_types = login_types
220
216221 # Login types and UI Auth types have a heavy overlap, but are not
217222 # necessarily identical. Login types have SSO (and other login types)
218223 # added in the rest layer, see synapse.rest.client.v1.login.LoginRestServerlet.on_GET.
225230 # as per `rc_login.failed_attempts`.
226231 self._failed_uia_attempts_ratelimiter = Ratelimiter(
227232 clock=self.clock,
233 rate_hz=self.hs.config.rc_login_failed_attempts.per_second,
234 burst_count=self.hs.config.rc_login_failed_attempts.burst_count,
235 )
236
237 # Ratelimitier for failed /login attempts
238 self._failed_login_attempts_ratelimiter = Ratelimiter(
239 clock=hs.get_clock(),
228240 rate_hz=self.hs.config.rc_login_failed_attempts.per_second,
229241 burst_count=self.hs.config.rc_login_failed_attempts.burst_count,
230242 )
641653 res = await checker.check_auth(authdict, clientip=clientip)
642654 return res
643655
644 # build a v1-login-style dict out of the authdict and fall back to the
645 # v1 code
646 user_id = authdict.get("user")
647
648 if user_id is None:
649 raise SynapseError(400, "", Codes.MISSING_PARAM)
650
651 (canonical_id, callback) = await self.validate_login(user_id, authdict)
656 # fall back to the v1 login flow
657 canonical_id, _ = await self.validate_login(authdict)
652658 return canonical_id
653659
654660 def _get_params_recaptcha(self) -> dict:
697703 }
698704
699705 async def get_access_token_for_user_id(
700 self, user_id: str, device_id: Optional[str], valid_until_ms: Optional[int]
701 ):
706 self,
707 user_id: str,
708 device_id: Optional[str],
709 valid_until_ms: Optional[int],
710 puppets_user_id: Optional[str] = None,
711 ) -> str:
702712 """
703713 Creates a new access token for the user with the given user ID.
704714
724734 fmt_expiry = time.strftime(
725735 " until %Y-%m-%d %H:%M:%S", time.localtime(valid_until_ms / 1000.0)
726736 )
727 logger.info("Logging in user %s on device %s%s", user_id, device_id, fmt_expiry)
737
738 if puppets_user_id:
739 logger.info(
740 "Logging in user %s as %s%s", user_id, puppets_user_id, fmt_expiry
741 )
742 else:
743 logger.info(
744 "Logging in user %s on device %s%s", user_id, device_id, fmt_expiry
745 )
728746
729747 await self.auth.check_auth_blocking(user_id)
730748
731749 access_token = self.macaroon_gen.generate_access_token(user_id)
732750 await self.store.add_access_token_to_user(
733 user_id, access_token, device_id, valid_until_ms
751 user_id=user_id,
752 token=access_token,
753 device_id=device_id,
754 valid_until_ms=valid_until_ms,
755 puppets_user_id=puppets_user_id,
734756 )
735757
736758 # the device *should* have been registered before we got here; however,
807829 return self._supported_login_types
808830
809831 async def validate_login(
810 self, username: str, login_submission: Dict[str, Any]
832 self, login_submission: Dict[str, Any], ratelimit: bool = False,
811833 ) -> Tuple[str, Optional[Callable[[Dict[str, str]], None]]]:
812834 """Authenticates the user for the /login API
813835
814 Also used by the user-interactive auth flow to validate
815 m.login.password auth types.
816
817 Args:
818 username: username supplied by the user
836 Also used by the user-interactive auth flow to validate auth types which don't
837 have an explicit UIA handler, including m.password.auth.
838
839 Args:
840 login_submission: the whole of the login submission
841 (including 'type' and other relevant fields)
842 ratelimit: whether to apply the failed_login_attempt ratelimiter
843 Returns:
844 A tuple of the canonical user id, and optional callback
845 to be called once the access token and device id are issued
846 Raises:
847 StoreError if there was a problem accessing the database
848 SynapseError if there was a problem with the request
849 LoginError if there was an authentication problem.
850 """
851 login_type = login_submission.get("type")
852 if not isinstance(login_type, str):
853 raise SynapseError(400, "Bad parameter: type", Codes.INVALID_PARAM)
854
855 # ideally, we wouldn't be checking the identifier unless we know we have a login
856 # method which uses it (https://github.com/matrix-org/synapse/issues/8836)
857 #
858 # But the auth providers' check_auth interface requires a username, so in
859 # practice we can only support login methods which we can map to a username
860 # anyway.
861
862 # special case to check for "password" for the check_password interface
863 # for the auth providers
864 password = login_submission.get("password")
865 if login_type == LoginType.PASSWORD:
866 if not self._password_enabled:
867 raise SynapseError(400, "Password login has been disabled.")
868 if not isinstance(password, str):
869 raise SynapseError(400, "Bad parameter: password", Codes.INVALID_PARAM)
870
871 # map old-school login fields into new-school "identifier" fields.
872 identifier_dict = convert_client_dict_legacy_fields_to_identifier(
873 login_submission
874 )
875
876 # convert phone type identifiers to generic threepids
877 if identifier_dict["type"] == "m.id.phone":
878 identifier_dict = login_id_phone_to_thirdparty(identifier_dict)
879
880 # convert threepid identifiers to user IDs
881 if identifier_dict["type"] == "m.id.thirdparty":
882 address = identifier_dict.get("address")
883 medium = identifier_dict.get("medium")
884
885 if medium is None or address is None:
886 raise SynapseError(400, "Invalid thirdparty identifier")
887
888 # For emails, canonicalise the address.
889 # We store all email addresses canonicalised in the DB.
890 # (See add_threepid in synapse/handlers/auth.py)
891 if medium == "email":
892 try:
893 address = canonicalise_email(address)
894 except ValueError as e:
895 raise SynapseError(400, str(e))
896
897 # We also apply account rate limiting using the 3PID as a key, as
898 # otherwise using 3PID bypasses the ratelimiting based on user ID.
899 if ratelimit:
900 self._failed_login_attempts_ratelimiter.ratelimit(
901 (medium, address), update=False
902 )
903
904 # Check for login providers that support 3pid login types
905 if login_type == LoginType.PASSWORD:
906 # we've already checked that there is a (valid) password field
907 assert isinstance(password, str)
908 (
909 canonical_user_id,
910 callback_3pid,
911 ) = await self.check_password_provider_3pid(medium, address, password)
912 if canonical_user_id:
913 # Authentication through password provider and 3pid succeeded
914 return canonical_user_id, callback_3pid
915
916 # No password providers were able to handle this 3pid
917 # Check local store
918 user_id = await self.hs.get_datastore().get_user_id_by_threepid(
919 medium, address
920 )
921 if not user_id:
922 logger.warning(
923 "unknown 3pid identifier medium %s, address %r", medium, address
924 )
925 # We mark that we've failed to log in here, as
926 # `check_password_provider_3pid` might have returned `None` due
927 # to an incorrect password, rather than the account not
928 # existing.
929 #
930 # If it returned None but the 3PID was bound then we won't hit
931 # this code path, which is fine as then the per-user ratelimit
932 # will kick in below.
933 if ratelimit:
934 self._failed_login_attempts_ratelimiter.can_do_action(
935 (medium, address)
936 )
937 raise LoginError(403, "", errcode=Codes.FORBIDDEN)
938
939 identifier_dict = {"type": "m.id.user", "user": user_id}
940
941 # by this point, the identifier should be an m.id.user: if it's anything
942 # else, we haven't understood it.
943 if identifier_dict["type"] != "m.id.user":
944 raise SynapseError(400, "Unknown login identifier type")
945
946 username = identifier_dict.get("user")
947 if not username:
948 raise SynapseError(400, "User identifier is missing 'user' key")
949
950 if username.startswith("@"):
951 qualified_user_id = username
952 else:
953 qualified_user_id = UserID(username, self.hs.hostname).to_string()
954
955 # Check if we've hit the failed ratelimit (but don't update it)
956 if ratelimit:
957 self._failed_login_attempts_ratelimiter.ratelimit(
958 qualified_user_id.lower(), update=False
959 )
960
961 try:
962 return await self._validate_userid_login(username, login_submission)
963 except LoginError:
964 # The user has failed to log in, so we need to update the rate
965 # limiter. Using `can_do_action` avoids us raising a ratelimit
966 # exception and masking the LoginError. The actual ratelimiting
967 # should have happened above.
968 if ratelimit:
969 self._failed_login_attempts_ratelimiter.can_do_action(
970 qualified_user_id.lower()
971 )
972 raise
973
974 async def _validate_userid_login(
975 self, username: str, login_submission: Dict[str, Any],
976 ) -> Tuple[str, Optional[Callable[[Dict[str, str]], None]]]:
977 """Helper for validate_login
978
979 Handles login, once we've mapped 3pids onto userids
980
981 Args:
982 username: the username, from the identifier dict
819983 login_submission: the whole of the login submission
820984 (including 'type' and other relevant fields)
821985 Returns:
826990 SynapseError if there was a problem with the request
827991 LoginError if there was an authentication problem.
828992 """
829
830993 if username.startswith("@"):
831994 qualified_user_id = username
832995 else:
833996 qualified_user_id = UserID(username, self.hs.hostname).to_string()
834997
835998 login_type = login_submission.get("type")
999 # we already checked that we have a valid login type
1000 assert isinstance(login_type, str)
1001
8361002 known_login_type = False
8371003
838 # special case to check for "password" for the check_password interface
839 # for the auth providers
840 password = login_submission.get("password")
841
842 if login_type == LoginType.PASSWORD:
843 if not self._password_enabled:
844 raise SynapseError(400, "Password login has been disabled.")
845 if not password:
846 raise SynapseError(400, "Missing parameter: password")
847
8481004 for provider in self.password_providers:
849 if hasattr(provider, "check_password") and login_type == LoginType.PASSWORD:
850 known_login_type = True
851 is_valid = await provider.check_password(qualified_user_id, password)
852 if is_valid:
853 return qualified_user_id, None
854
855 if not hasattr(provider, "get_supported_login_types") or not hasattr(
856 provider, "check_auth"
857 ):
858 # this password provider doesn't understand custom login types
859 continue
860
8611005 supported_login_types = provider.get_supported_login_types()
8621006 if login_type not in supported_login_types:
8631007 # this password provider doesn't understand this login type
8821026
8831027 result = await provider.check_auth(username, login_type, login_dict)
8841028 if result:
885 if isinstance(result, str):
886 result = (result, None)
8871029 return result
8881030
8891031 if login_type == LoginType.PASSWORD and self.hs.config.password_localdb_enabled:
8901032 known_login_type = True
8911033
1034 # we've already checked that there is a (valid) password field
1035 password = login_submission["password"]
1036 assert isinstance(password, str)
1037
8921038 canonical_user_id = await self._check_local_password(
893 qualified_user_id, password # type: ignore
1039 qualified_user_id, password
8941040 )
8951041
8961042 if canonical_user_id:
9211067 unsuccessful, `user_id` and `callback` are both `None`.
9221068 """
9231069 for provider in self.password_providers:
924 if hasattr(provider, "check_3pid_auth"):
925 # This function is able to return a deferred that either
926 # resolves None, meaning authentication failure, or upon
927 # success, to a str (which is the user_id) or a tuple of
928 # (user_id, callback_func), where callback_func should be run
929 # after we've finished everything else
930 result = await provider.check_3pid_auth(medium, address, password)
931 if result:
932 # Check if the return value is a str or a tuple
933 if isinstance(result, str):
934 # If it's a str, set callback function to None
935 result = (result, None)
936 return result
1070 result = await provider.check_3pid_auth(medium, address, password)
1071 if result:
1072 return result
9371073
9381074 return None, None
9391075
9911127
9921128 # see if any of our auth providers want to know about this
9931129 for provider in self.password_providers:
994 if hasattr(provider, "on_logged_out"):
995 # This might return an awaitable, if it does block the log out
996 # until it completes.
997 result = provider.on_logged_out(
998 user_id=user_info.user_id,
999 device_id=user_info.device_id,
1000 access_token=access_token,
1001 )
1002 if inspect.isawaitable(result):
1003 await result
1130 await provider.on_logged_out(
1131 user_id=user_info.user_id,
1132 device_id=user_info.device_id,
1133 access_token=access_token,
1134 )
10041135
10051136 # delete pushers associated with this access token
10061137 if user_info.token_id is not None:
10291160
10301161 # see if any of our auth providers want to know about this
10311162 for provider in self.password_providers:
1032 if hasattr(provider, "on_logged_out"):
1033 for token, token_id, device_id in tokens_and_devices:
1034 await provider.on_logged_out(
1035 user_id=user_id, device_id=device_id, access_token=token
1036 )
1163 for token, token_id, device_id in tokens_and_devices:
1164 await provider.on_logged_out(
1165 user_id=user_id, device_id=device_id, access_token=token
1166 )
10371167
10381168 # delete pushers associated with the access tokens
10391169 await self.hs.get_pusherpool().remove_pushers_by_access_token(
13571487 macaroon.add_first_party_caveat("gen = 1")
13581488 macaroon.add_first_party_caveat("user_id = %s" % (user_id,))
13591489 return macaroon
1490
1491
1492 class PasswordProvider:
1493 """Wrapper for a password auth provider module
1494
1495 This class abstracts out all of the backwards-compatibility hacks for
1496 password providers, to provide a consistent interface.
1497 """
1498
1499 @classmethod
1500 def load(cls, module, config, module_api: ModuleApi) -> "PasswordProvider":
1501 try:
1502 pp = module(config=config, account_handler=module_api)
1503 except Exception as e:
1504 logger.error("Error while initializing %r: %s", module, e)
1505 raise
1506 return cls(pp, module_api)
1507
1508 def __init__(self, pp, module_api: ModuleApi):
1509 self._pp = pp
1510 self._module_api = module_api
1511
1512 self._supported_login_types = {}
1513
1514 # grandfather in check_password support
1515 if hasattr(self._pp, "check_password"):
1516 self._supported_login_types[LoginType.PASSWORD] = ("password",)
1517
1518 g = getattr(self._pp, "get_supported_login_types", None)
1519 if g:
1520 self._supported_login_types.update(g())
1521
1522 def __str__(self):
1523 return str(self._pp)
1524
1525 def get_supported_login_types(self) -> Mapping[str, Iterable[str]]:
1526 """Get the login types supported by this password provider
1527
1528 Returns a map from a login type identifier (such as m.login.password) to an
1529 iterable giving the fields which must be provided by the user in the submission
1530 to the /login API.
1531
1532 This wrapper adds m.login.password to the list if the underlying password
1533 provider supports the check_password() api.
1534 """
1535 return self._supported_login_types
1536
1537 async def check_auth(
1538 self, username: str, login_type: str, login_dict: JsonDict
1539 ) -> Optional[Tuple[str, Optional[Callable]]]:
1540 """Check if the user has presented valid login credentials
1541
1542 This wrapper also calls check_password() if the underlying password provider
1543 supports the check_password() api and the login type is m.login.password.
1544
1545 Args:
1546 username: user id presented by the client. Either an MXID or an unqualified
1547 username.
1548
1549 login_type: the login type being attempted - one of the types returned by
1550 get_supported_login_types()
1551
1552 login_dict: the dictionary of login secrets passed by the client.
1553
1554 Returns: (user_id, callback) where `user_id` is the fully-qualified mxid of the
1555 user, and `callback` is an optional callback which will be called with the
1556 result from the /login call (including access_token, device_id, etc.)
1557 """
1558 # first grandfather in a call to check_password
1559 if login_type == LoginType.PASSWORD:
1560 g = getattr(self._pp, "check_password", None)
1561 if g:
1562 qualified_user_id = self._module_api.get_qualified_user_id(username)
1563 is_valid = await self._pp.check_password(
1564 qualified_user_id, login_dict["password"]
1565 )
1566 if is_valid:
1567 return qualified_user_id, None
1568
1569 g = getattr(self._pp, "check_auth", None)
1570 if not g:
1571 return None
1572 result = await g(username, login_type, login_dict)
1573
1574 # Check if the return value is a str or a tuple
1575 if isinstance(result, str):
1576 # If it's a str, set callback function to None
1577 return result, None
1578
1579 return result
1580
1581 async def check_3pid_auth(
1582 self, medium: str, address: str, password: str
1583 ) -> Optional[Tuple[str, Optional[Callable]]]:
1584 g = getattr(self._pp, "check_3pid_auth", None)
1585 if not g:
1586 return None
1587
1588 # This function is able to return a deferred that either
1589 # resolves None, meaning authentication failure, or upon
1590 # success, to a str (which is the user_id) or a tuple of
1591 # (user_id, callback_func), where callback_func should be run
1592 # after we've finished everything else
1593 result = await g(medium, address, password)
1594
1595 # Check if the return value is a str or a tuple
1596 if isinstance(result, str):
1597 # If it's a str, set callback function to None
1598 return result, None
1599
1600 return result
1601
1602 async def on_logged_out(
1603 self, user_id: str, device_id: Optional[str], access_token: str
1604 ) -> None:
1605 g = getattr(self._pp, "on_logged_out", None)
1606 if not g:
1607 return
1608
1609 # This might return an awaitable, if it does block the log out
1610 # until it completes.
1611 result = g(user_id=user_id, device_id=device_id, access_token=access_token,)
1612 if inspect.isawaitable(result):
1613 await result
1313 # limitations under the License.
1414 import logging
1515 import urllib
16 from typing import Dict, Optional, Tuple
16 from typing import TYPE_CHECKING, Dict, Optional, Tuple
1717 from xml.etree import ElementTree as ET
1818
1919 from twisted.web.client import PartialDownloadError
2222 from synapse.http.site import SynapseRequest
2323 from synapse.types import UserID, map_username_to_mxid_localpart
2424
25 if TYPE_CHECKING:
26 from synapse.app.homeserver import HomeServer
27
2528 logger = logging.getLogger(__name__)
2629
2730
3033 Utility class for to handle the response from a CAS SSO service.
3134
3235 Args:
33 hs (synapse.server.HomeServer)
36 hs
3437 """
3538
36 def __init__(self, hs):
39 def __init__(self, hs: "HomeServer"):
3740 self.hs = hs
3841 self._hostname = hs.hostname
3942 self._auth_handler = hs.get_auth_handler()
199202 args["session"] = session
200203 username, user_display_name = await self._validate_ticket(ticket, args)
201204
202 localpart = map_username_to_mxid_localpart(username)
205 # Pull out the user-agent and IP from the request.
206 user_agent = request.get_user_agent("")
207 ip_address = self.hs.get_ip_from_request(request)
208
209 # Get the matrix ID from the CAS username.
210 user_id = await self._map_cas_user_to_matrix_user(
211 username, user_display_name, user_agent, ip_address
212 )
213
214 if session:
215 await self._auth_handler.complete_sso_ui_auth(
216 user_id, session, request,
217 )
218 else:
219 # If this not a UI auth request than there must be a redirect URL.
220 assert client_redirect_url
221
222 await self._auth_handler.complete_sso_login(
223 user_id, request, client_redirect_url
224 )
225
226 async def _map_cas_user_to_matrix_user(
227 self,
228 remote_user_id: str,
229 display_name: Optional[str],
230 user_agent: str,
231 ip_address: str,
232 ) -> str:
233 """
234 Given a CAS username, retrieve the user ID for it and possibly register the user.
235
236 Args:
237 remote_user_id: The username from the CAS response.
238 display_name: The display name from the CAS response.
239 user_agent: The user agent of the client making the request.
240 ip_address: The IP address of the client making the request.
241
242 Returns:
243 The user ID associated with this response.
244 """
245
246 localpart = map_username_to_mxid_localpart(remote_user_id)
203247 user_id = UserID(localpart, self._hostname).to_string()
204248 registered_user_id = await self._auth_handler.check_user_exists(user_id)
205249
206 if session:
207 await self._auth_handler.complete_sso_ui_auth(
208 registered_user_id, session, request,
209 )
210
211 else:
212 if not registered_user_id:
213 # Pull out the user-agent and IP from the request.
214 user_agent = request.get_user_agent("")
215 ip_address = self.hs.get_ip_from_request(request)
216
217 registered_user_id = await self._registration_handler.register_user(
218 localpart=localpart,
219 default_display_name=user_display_name,
220 user_agent_ips=(user_agent, ip_address),
221 )
222
223 await self._auth_handler.complete_sso_login(
224 registered_user_id, request, client_redirect_url
225 )
250 # If the user does not exist, register it.
251 if not registered_user_id:
252 registered_user_id = await self._registration_handler.register_user(
253 localpart=localpart,
254 default_display_name=display_name,
255 user_agent_ips=[(user_agent, ip_address)],
256 )
257
258 return registered_user_id
3838 self._room_member_handler = hs.get_room_member_handler()
3939 self._identity_handler = hs.get_identity_handler()
4040 self.user_directory_handler = hs.get_user_directory_handler()
41 self._server_name = hs.hostname
4142
4243 # Flag that indicates whether the process to part users from rooms is running
4344 self._user_parter_running = False
151152 for room in pending_invites:
152153 try:
153154 await self._room_member_handler.update_membership(
154 create_requester(user),
155 create_requester(user, authenticated_entity=self._server_name),
155156 user,
156157 room.room_id,
157158 "leave",
207208 logger.info("User parter parting %r from %r", user_id, room_id)
208209 try:
209210 await self._room_member_handler.update_membership(
210 create_requester(user),
211 create_requester(user, authenticated_entity=self._server_name),
211212 user,
212213 room_id,
213214 "leave",
5454 from synapse.events.snapshot import EventContext
5555 from synapse.events.validator import EventValidator
5656 from synapse.handlers._base import BaseHandler
57 from synapse.http.servlet import assert_params_in_dict
5758 from synapse.logging.context import (
5859 make_deferred_yieldable,
5960 nested_logging_context,
6667 from synapse.replication.http.federation import (
6768 ReplicationCleanRoomRestServlet,
6869 ReplicationFederationSendEventsRestServlet,
69 ReplicationStoreRoomOnInviteRestServlet,
70 ReplicationStoreRoomOnOutlierMembershipRestServlet,
7071 )
7172 from synapse.state import StateResolutionStore
7273 from synapse.storage.databases.main.events_worker import EventRedactBehaviour
151152 self._user_device_resync = ReplicationUserDevicesResyncRestServlet.make_client(
152153 hs
153154 )
154 self._maybe_store_room_on_invite = ReplicationStoreRoomOnInviteRestServlet.make_client(
155 self._maybe_store_room_on_outlier_membership = ReplicationStoreRoomOnOutlierMembershipRestServlet.make_client(
155156 hs
156157 )
157158 else:
158159 self._device_list_updater = hs.get_device_handler().device_list_updater
159 self._maybe_store_room_on_invite = self.store.maybe_store_room_on_invite
160 self._maybe_store_room_on_outlier_membership = (
161 self.store.maybe_store_room_on_outlier_membership
162 )
160163
161164 # When joining a room we need to queue any events for that room up.
162165 # For each room, a list of (pdu, origin) tuples.
16161619 # keep a record of the room version, if we don't yet know it.
16171620 # (this may get overwritten if we later get a different room version in a
16181621 # join dance).
1619 await self._maybe_store_room_on_invite(
1622 await self._maybe_store_room_on_outlier_membership(
16201623 room_id=event.room_id, room_version=room_version
16211624 )
16221625
26852688 )
26862689
26872690 async def on_exchange_third_party_invite_request(
2688 self, room_id: str, event_dict: JsonDict
2691 self, event_dict: JsonDict
26892692 ) -> None:
26902693 """Handle an exchange_third_party_invite request from a remote server
26912694
26932696 into a normal m.room.member invite.
26942697
26952698 Args:
2696 room_id: The ID of the room.
2697
2698 event_dict (dict[str, Any]): Dictionary containing the event body.
2699
2700 """
2701 room_version = await self.store.get_room_version_id(room_id)
2699 event_dict: Dictionary containing the event body.
2700
2701 """
2702 assert_params_in_dict(event_dict, ["room_id"])
2703 room_version = await self.store.get_room_version_id(event_dict["room_id"])
27022704
27032705 # NB: event_dict has a particular specced format we might need to fudge
27042706 # if we change event formats too much.
353353 raise SynapseError(500, "An error was encountered when sending the email")
354354
355355 token_expires = (
356 self.hs.clock.time_msec() + self.hs.config.email_validation_token_lifetime
356 self.hs.get_clock().time_msec()
357 + self.hs.config.email_validation_token_lifetime
357358 )
358359
359360 await self.store.start_or_continue_validation_session(
471471 Returns:
472472 Tuple of created event, Context
473473 """
474 await self.auth.check_auth_blocking(requester.user.to_string())
474 await self.auth.check_auth_blocking(requester=requester)
475475
476476 if event_dict["type"] == EventTypes.Create and event_dict["state_key"] == "":
477477 room_version = event_dict["content"]["room_version"]
618618 if requester.app_service is not None:
619619 return
620620
621 user_id = requester.user.to_string()
621 user_id = requester.authenticated_entity
622 if not user_id.startswith("@"):
623 # The authenticated entity might not be a user, e.g. if it's the
624 # server puppetting the user.
625 return
626
627 user = UserID.from_string(user_id)
622628
623629 # exempt the system notices user
624630 if (
638644 if u["consent_version"] == self.config.user_consent_version:
639645 return
640646
641 consent_uri = self._consent_uri_builder.build_user_consent_uri(
642 requester.user.localpart
643 )
647 consent_uri = self._consent_uri_builder.build_user_consent_uri(user.localpart)
644648 msg = self._block_events_without_consent_error % {"consent_uri": consent_uri}
645649 raise ConsentNotGivenError(msg=msg, consent_uri=consent_uri)
646650
12511255 for user_id in members:
12521256 if not self.hs.is_mine_id(user_id):
12531257 continue
1254 requester = create_requester(user_id)
1258 requester = create_requester(user_id, authenticated_entity=self.server_name)
12551259 try:
12561260 event, context = await self.create_event(
12571261 requester,
12721276 requester, event, context, ratelimit=False, ignore_shadow_ban=True,
12731277 )
12741278 return True
1275 except ConsentNotGivenError:
1276 logger.info(
1277 "Failed to send dummy event into room %s for user %s due to "
1278 "lack of consent. Will try another user" % (room_id, user_id)
1279 )
12801279 except AuthError:
12811280 logger.info(
12821281 "Failed to send dummy event into room %s for user %s due to "
1111 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212 # See the License for the specific language governing permissions and
1313 # limitations under the License.
14 import inspect
1415 import logging
1516 from typing import TYPE_CHECKING, Dict, Generic, List, Optional, Tuple, TypeVar
1617 from urllib.parse import urlencode
3334 from twisted.web.client import readBody
3435
3536 from synapse.config import ConfigError
36 from synapse.http.server import respond_with_html
37 from synapse.handlers._base import BaseHandler
38 from synapse.handlers.sso import MappingException, UserAttributes
3739 from synapse.http.site import SynapseRequest
3840 from synapse.logging.context import make_deferred_yieldable
3941 from synapse.types import JsonDict, UserID, map_username_to_mxid_localpart
8284 return self.error
8385
8486
85 class MappingException(Exception):
86 """Used to catch errors when mapping the UserInfo object
87 """
88
89
90 class OidcHandler:
87 class OidcHandler(BaseHandler):
9188 """Handles requests related to the OpenID Connect login flow.
9289 """
9390
9491 def __init__(self, hs: "HomeServer"):
95 self.hs = hs
92 super().__init__(hs)
9693 self._callback_url = hs.config.oidc_callback_url # type: str
9794 self._scopes = hs.config.oidc_scopes # type: List[str]
9895 self._user_profile_method = hs.config.oidc_user_profile_method # type: str
119116 self._http_client = hs.get_proxied_http_client()
120117 self._auth_handler = hs.get_auth_handler()
121118 self._registration_handler = hs.get_registration_handler()
122 self._datastore = hs.get_datastore()
123 self._clock = hs.get_clock()
124 self._hostname = hs.hostname # type: str
125119 self._server_name = hs.config.server_name # type: str
126120 self._macaroon_secret_key = hs.config.macaroon_secret_key
127 self._error_template = hs.config.sso_error_template
128121
129122 # identifier for the external_ids table
130123 self._auth_provider_id = "oidc"
131124
132 def _render_error(
133 self, request, error: str, error_description: Optional[str] = None
134 ) -> None:
135 """Render the error template and respond to the request with it.
136
137 This is used to show errors to the user. The template of this page can
138 be found under `synapse/res/templates/sso_error.html`.
139
140 Args:
141 request: The incoming request from the browser.
142 We'll respond with an HTML page describing the error.
143 error: A technical identifier for this error. Those include
144 well-known OAuth2/OIDC error types like invalid_request or
145 access_denied.
146 error_description: A human-readable description of the error.
147 """
148 html = self._error_template.render(
149 error=error, error_description=error_description
150 )
151 respond_with_html(request, 400, html)
125 self._sso_handler = hs.get_sso_handler()
152126
153127 def _validate_metadata(self):
154128 """Verifies the provider metadata.
570544
571545 Since we might want to display OIDC-related errors in a user-friendly
572546 way, we don't raise SynapseError from here. Instead, we call
573 ``self._render_error`` which displays an HTML page for the error.
547 ``self._sso_handler.render_error`` which displays an HTML page for the error.
574548
575549 Most of the OpenID Connect logic happens here:
576550
608582 if error != "access_denied":
609583 logger.error("Error from the OIDC provider: %s %s", error, description)
610584
611 self._render_error(request, error, description)
585 self._sso_handler.render_error(request, error, description)
612586 return
613587
614588 # otherwise, it is presumably a successful response. see:
618592 session = request.getCookie(SESSION_COOKIE_NAME) # type: Optional[bytes]
619593 if session is None:
620594 logger.info("No session cookie found")
621 self._render_error(request, "missing_session", "No session cookie found")
595 self._sso_handler.render_error(
596 request, "missing_session", "No session cookie found"
597 )
622598 return
623599
624600 # Remove the cookie. There is a good chance that if the callback failed
636612 # Check for the state query parameter
637613 if b"state" not in request.args:
638614 logger.info("State parameter is missing")
639 self._render_error(request, "invalid_request", "State parameter is missing")
615 self._sso_handler.render_error(
616 request, "invalid_request", "State parameter is missing"
617 )
640618 return
641619
642620 state = request.args[b"state"][0].decode()
650628 ) = self._verify_oidc_session_token(session, state)
651629 except MacaroonDeserializationException as e:
652630 logger.exception("Invalid session")
653 self._render_error(request, "invalid_session", str(e))
631 self._sso_handler.render_error(request, "invalid_session", str(e))
654632 return
655633 except MacaroonInvalidSignatureException as e:
656634 logger.exception("Could not verify session")
657 self._render_error(request, "mismatching_session", str(e))
635 self._sso_handler.render_error(request, "mismatching_session", str(e))
658636 return
659637
660638 # Exchange the code with the provider
661639 if b"code" not in request.args:
662640 logger.info("Code parameter is missing")
663 self._render_error(request, "invalid_request", "Code parameter is missing")
641 self._sso_handler.render_error(
642 request, "invalid_request", "Code parameter is missing"
643 )
664644 return
665645
666646 logger.debug("Exchanging code")
669649 token = await self._exchange_code(code)
670650 except OidcError as e:
671651 logger.exception("Could not exchange code")
672 self._render_error(request, e.error, e.error_description)
652 self._sso_handler.render_error(request, e.error, e.error_description)
673653 return
674654
675655 logger.debug("Successfully obtained OAuth2 access token")
682662 userinfo = await self._fetch_userinfo(token)
683663 except Exception as e:
684664 logger.exception("Could not fetch userinfo")
685 self._render_error(request, "fetch_error", str(e))
665 self._sso_handler.render_error(request, "fetch_error", str(e))
686666 return
687667 else:
688668 logger.debug("Extracting userinfo from id_token")
690670 userinfo = await self._parse_id_token(token, nonce=nonce)
691671 except Exception as e:
692672 logger.exception("Invalid id_token")
693 self._render_error(request, "invalid_token", str(e))
673 self._sso_handler.render_error(request, "invalid_token", str(e))
694674 return
695675
696676 # Pull out the user-agent and IP from the request.
704684 )
705685 except MappingException as e:
706686 logger.exception("Could not map user")
707 self._render_error(request, "mapping_error", str(e))
687 self._sso_handler.render_error(request, "mapping_error", str(e))
708688 return
709689
710690 # Mapping providers might not have get_extra_attributes: only call this
769749 macaroon.add_first_party_caveat(
770750 "ui_auth_session_id = %s" % (ui_auth_session_id,)
771751 )
772 now = self._clock.time_msec()
752 now = self.clock.time_msec()
773753 expiry = now + duration_in_ms
774754 macaroon.add_first_party_caveat("time < %d" % (expiry,))
775755
844824 if not caveat.startswith(prefix):
845825 return False
846826 expiry = int(caveat[len(prefix) :])
847 now = self._clock.time_msec()
827 now = self.clock.time_msec()
848828 return now < expiry
849829
850830 async def _map_userinfo_to_user(
884864 # to be strings.
885865 remote_user_id = str(remote_user_id)
886866
887 logger.info(
888 "Looking for existing mapping for user %s:%s",
867 # Older mapping providers don't accept the `failures` argument, so we
868 # try and detect support.
869 mapper_signature = inspect.signature(
870 self._user_mapping_provider.map_user_attributes
871 )
872 supports_failures = "failures" in mapper_signature.parameters
873
874 async def oidc_response_to_user_attributes(failures: int) -> UserAttributes:
875 """
876 Call the mapping provider to map the OIDC userinfo and token to user attributes.
877
878 This is backwards compatibility for abstraction for the SSO handler.
879 """
880 if supports_failures:
881 attributes = await self._user_mapping_provider.map_user_attributes(
882 userinfo, token, failures
883 )
884 else:
885 # If the mapping provider does not support processing failures,
886 # do not continually generate the same Matrix ID since it will
887 # continue to already be in use. Note that the error raised is
888 # arbitrary and will get turned into a MappingException.
889 if failures:
890 raise MappingException(
891 "Mapping provider does not support de-duplicating Matrix IDs"
892 )
893
894 attributes = await self._user_mapping_provider.map_user_attributes( # type: ignore
895 userinfo, token
896 )
897
898 return UserAttributes(**attributes)
899
900 async def grandfather_existing_users() -> Optional[str]:
901 if self._allow_existing_users:
902 # If allowing existing users we want to generate a single localpart
903 # and attempt to match it.
904 attributes = await oidc_response_to_user_attributes(failures=0)
905
906 user_id = UserID(attributes.localpart, self.server_name).to_string()
907 users = await self.store.get_users_by_id_case_insensitive(user_id)
908 if users:
909 # If an existing matrix ID is returned, then use it.
910 if len(users) == 1:
911 previously_registered_user_id = next(iter(users))
912 elif user_id in users:
913 previously_registered_user_id = user_id
914 else:
915 # Do not attempt to continue generating Matrix IDs.
916 raise MappingException(
917 "Attempted to login as '{}' but it matches more than one user inexactly: {}".format(
918 user_id, users
919 )
920 )
921
922 return previously_registered_user_id
923
924 return None
925
926 return await self._sso_handler.get_mxid_from_sso(
889927 self._auth_provider_id,
890928 remote_user_id,
891 )
892
893 registered_user_id = await self._datastore.get_user_by_external_id(
894 self._auth_provider_id, remote_user_id,
895 )
896
897 if registered_user_id is not None:
898 logger.info("Found existing mapping %s", registered_user_id)
899 return registered_user_id
900
901 try:
902 attributes = await self._user_mapping_provider.map_user_attributes(
903 userinfo, token
904 )
905 except Exception as e:
906 raise MappingException(
907 "Could not extract user attributes from OIDC response: " + str(e)
908 )
909
910 logger.debug(
911 "Retrieved user attributes from user mapping provider: %r", attributes
912 )
913
914 if not attributes["localpart"]:
915 raise MappingException("localpart is empty")
916
917 localpart = map_username_to_mxid_localpart(attributes["localpart"])
918
919 user_id = UserID(localpart, self._hostname).to_string()
920 users = await self._datastore.get_users_by_id_case_insensitive(user_id)
921 if users:
922 if self._allow_existing_users:
923 if len(users) == 1:
924 registered_user_id = next(iter(users))
925 elif user_id in users:
926 registered_user_id = user_id
927 else:
928 raise MappingException(
929 "Attempted to login as '{}' but it matches more than one user inexactly: {}".format(
930 user_id, list(users.keys())
931 )
932 )
933 else:
934 # This mxid is taken
935 raise MappingException("mxid '{}' is already taken".format(user_id))
936 else:
937 # It's the first time this user is logging in and the mapped mxid was
938 # not taken, register the user
939 registered_user_id = await self._registration_handler.register_user(
940 localpart=localpart,
941 default_display_name=attributes["display_name"],
942 user_agent_ips=(user_agent, ip_address),
943 )
944 await self._datastore.record_user_external_id(
945 self._auth_provider_id, remote_user_id, registered_user_id,
946 )
947 return registered_user_id
948
949
950 UserAttribute = TypedDict(
951 "UserAttribute", {"localpart": str, "display_name": Optional[str]}
929 user_agent,
930 ip_address,
931 oidc_response_to_user_attributes,
932 grandfather_existing_users,
933 )
934
935
936 UserAttributeDict = TypedDict(
937 "UserAttributeDict", {"localpart": str, "display_name": Optional[str]}
952938 )
953939 C = TypeVar("C")
954940
991977 raise NotImplementedError()
992978
993979 async def map_user_attributes(
994 self, userinfo: UserInfo, token: Token
995 ) -> UserAttribute:
980 self, userinfo: UserInfo, token: Token, failures: int
981 ) -> UserAttributeDict:
996982 """Map a `UserInfo` object into user attributes.
997983
998984 Args:
999985 userinfo: An object representing the user given by the OIDC provider
1000986 token: A dict with the tokens returned by the provider
987 failures: How many times a call to this function with this
988 UserInfo has resulted in a failure.
1001989
1002990 Returns:
1003991 A dict containing the ``localpart`` and (optionally) the ``display_name``
10971085 return userinfo[self._config.subject_claim]
10981086
10991087 async def map_user_attributes(
1100 self, userinfo: UserInfo, token: Token
1101 ) -> UserAttribute:
1088 self, userinfo: UserInfo, token: Token, failures: int
1089 ) -> UserAttributeDict:
11021090 localpart = self._config.localpart_template.render(user=userinfo).strip()
1091
1092 # Ensure only valid characters are included in the MXID.
1093 localpart = map_username_to_mxid_localpart(localpart)
1094
1095 # Append suffix integer if last call to this function failed to produce
1096 # a usable mxid.
1097 localpart += str(failures) if failures else ""
11031098
11041099 display_name = None # type: Optional[str]
11051100 if self._config.display_name_template is not None:
11101105 if display_name == "":
11111106 display_name = None
11121107
1113 return UserAttribute(localpart=localpart, display_name=display_name)
1108 return UserAttributeDict(localpart=localpart, display_name=display_name)
11141109
11151110 async def get_extra_attributes(self, userinfo: UserInfo, token: Token) -> JsonDict:
11161111 extras = {} # type: Dict[str, str]
298298 """
299299 return self._purges_by_id.get(purge_id)
300300
301 async def purge_room(self, room_id: str) -> None:
302 """Purge the given room from the database"""
301 async def purge_room(self, room_id: str, force: bool = False) -> None:
302 """Purge the given room from the database.
303
304 Args:
305 room_id: room to be purged
306 force: set true to skip checking for joined users.
307 """
303308 with await self.pagination_lock.write(room_id):
304309 # check we know about the room
305310 await self.store.get_room_version_id(room_id)
306311
307312 # first check that we have no users in this room
308 joined = await self.store.is_host_joined(room_id, self._server_name)
309
310 if joined:
311 raise SynapseError(400, "Users are still joined to this room")
313 if not force:
314 joined = await self.store.is_host_joined(room_id, self._server_name)
315 if joined:
316 raise SynapseError(400, "Users are still joined to this room")
312317
313318 await self.storage.purge_events.purge_room(room_id)
314319
2424 import abc
2525 import logging
2626 from contextlib import contextmanager
27 from typing import Dict, Iterable, List, Set, Tuple
27 from typing import TYPE_CHECKING, Dict, Iterable, List, Set, Tuple
2828
2929 from prometheus_client import Counter
3030 from typing_extensions import ContextManager
4545 from synapse.util.metrics import Measure
4646 from synapse.util.wheel_timer import WheelTimer
4747
48 MYPY = False
49 if MYPY:
48 if TYPE_CHECKING:
5049 from synapse.server import HomeServer
5150
5251 logger = logging.getLogger(__name__)
205205 # the join event to update the displayname in the rooms.
206206 # This must be done by the target user himself.
207207 if by_admin:
208 requester = create_requester(target_user)
208 requester = create_requester(
209 target_user, authenticated_entity=requester.authenticated_entity,
210 )
209211
210212 await self.store.set_profile_displayname(
211213 target_user.localpart, displayname_to_set
285287
286288 # Same like set_displayname
287289 if by_admin:
288 requester = create_requester(target_user)
290 requester = create_requester(
291 target_user, authenticated_entity=requester.authenticated_entity
292 )
289293
290294 await self.store.set_profile_avatar_url(target_user.localpart, new_avatar_url)
291295
157157 if from_key == to_key:
158158 return [], to_key
159159
160 # We first need to fetch all new receipts
160 # Fetch all read receipts for all rooms, up to a limit of 100. This is ordered
161 # by most recent.
161162 rooms_to_events = await self.store.get_linearized_receipts_for_all_rooms(
162163 from_key=from_key, to_key=to_key
163164 )
1414
1515 """Contains functions for registering clients."""
1616 import logging
17 from typing import TYPE_CHECKING, List, Optional, Tuple
1718
1819 from synapse import types
1920 from synapse.api.constants import MAX_USERID_LENGTH, EventTypes, JoinRules, LoginType
2021 from synapse.api.errors import AuthError, Codes, ConsentNotGivenError, SynapseError
22 from synapse.appservice import ApplicationService
2123 from synapse.config.server import is_threepid_reserved
2224 from synapse.http.servlet import assert_params_in_dict
2325 from synapse.replication.http.login import RegisterDeviceReplicationServlet
3133
3234 from ._base import BaseHandler
3335
36 if TYPE_CHECKING:
37 from synapse.app.homeserver import HomeServer
38
3439 logger = logging.getLogger(__name__)
3540
3641
3742 class RegistrationHandler(BaseHandler):
38 def __init__(self, hs):
39 """
40
41 Args:
42 hs (synapse.server.HomeServer):
43 """
43 def __init__(self, hs: "HomeServer"):
4444 super().__init__(hs)
4545 self.hs = hs
4646 self.auth = hs.get_auth()
5151 self.ratelimiter = hs.get_registration_ratelimiter()
5252 self.macaroon_gen = hs.get_macaroon_generator()
5353 self._server_notices_mxid = hs.config.server_notices_mxid
54 self._server_name = hs.hostname
5455
5556 self.spam_checker = hs.get_spam_checker()
5657
6970 self.session_lifetime = hs.config.session_lifetime
7071
7172 async def check_username(
72 self, localpart, guest_access_token=None, assigned_user_id=None
73 self,
74 localpart: str,
75 guest_access_token: Optional[str] = None,
76 assigned_user_id: Optional[str] = None,
7377 ):
7478 if types.contains_invalid_mxid_characters(localpart):
7579 raise SynapseError(
138142
139143 async def register_user(
140144 self,
141 localpart=None,
142 password_hash=None,
143 guest_access_token=None,
144 make_guest=False,
145 admin=False,
146 threepid=None,
147 user_type=None,
148 default_display_name=None,
149 address=None,
150 bind_emails=[],
151 by_admin=False,
152 user_agent_ips=None,
153 ):
145 localpart: Optional[str] = None,
146 password_hash: Optional[str] = None,
147 guest_access_token: Optional[str] = None,
148 make_guest: bool = False,
149 admin: bool = False,
150 threepid: Optional[dict] = None,
151 user_type: Optional[str] = None,
152 default_display_name: Optional[str] = None,
153 address: Optional[str] = None,
154 bind_emails: List[str] = [],
155 by_admin: bool = False,
156 user_agent_ips: Optional[List[Tuple[str, str]]] = None,
157 ) -> str:
154158 """Registers a new client on the server.
155159
156160 Args:
157161 localpart: The local part of the user ID to register. If None,
158162 one will be generated.
159 password_hash (str|None): The hashed password to assign to this user so they can
163 password_hash: The hashed password to assign to this user so they can
160164 login again. This can be None which means they cannot login again
161165 via a password (e.g. the user is an application service user).
162 user_type (str|None): type of user. One of the values from
166 guest_access_token: The access token used when this was a guest
167 account.
168 make_guest: True if the the new user should be guest,
169 false to add a regular user account.
170 admin: True if the user should be registered as a server admin.
171 threepid: The threepid used for registering, if any.
172 user_type: type of user. One of the values from
163173 api.constants.UserTypes, or None for a normal user.
164 default_display_name (unicode|None): if set, the new user's displayname
174 default_display_name: if set, the new user's displayname
165175 will be set to this. Defaults to 'localpart'.
166 address (str|None): the IP address used to perform the registration.
167 bind_emails (List[str]): list of emails to bind to this account.
168 by_admin (bool): True if this registration is being made via the
176 address: the IP address used to perform the registration.
177 bind_emails: list of emails to bind to this account.
178 by_admin: True if this registration is being made via the
169179 admin api, otherwise False.
170 user_agent_ips (List[(str, str)]): Tuples of IP addresses and user-agents used
180 user_agent_ips: Tuples of IP addresses and user-agents used
171181 during the registration process.
172182 Returns:
173 str: user_id
183 The registere user_id.
174184 Raises:
175185 SynapseError if there was a problem registering.
176186 """
234244 else:
235245 # autogen a sequential user ID
236246 fail_count = 0
237 user = None
238 while not user:
247 # If a default display name is not given, generate one.
248 generate_display_name = default_display_name is None
249 # This breaks on successful registration *or* errors after 10 failures.
250 while True:
239251 # Fail after being unable to find a suitable ID a few times
240252 if fail_count > 10:
241253 raise SynapseError(500, "Unable to find a suitable guest user ID")
244256 user = UserID(localpart, self.hs.hostname)
245257 user_id = user.to_string()
246258 self.check_user_id_not_appservice_exclusive(user_id)
247 if default_display_name is None:
259 if generate_display_name:
248260 default_display_name = localpart
249261 try:
250262 await self.register_with_store(
260272 break
261273 except SynapseError:
262274 # if user id is taken, just generate another
263 user = None
264 user_id = None
265275 fail_count += 1
266276
267277 if not self.hs.config.user_consent_at_registration:
293303
294304 return user_id
295305
296 async def _create_and_join_rooms(self, user_id: str):
306 async def _create_and_join_rooms(self, user_id: str) -> None:
297307 """
298308 Create the auto-join rooms and join or invite the user to them.
299309
316326 requires_join = False
317327 if self.hs.config.registration.auto_join_user_id:
318328 fake_requester = create_requester(
319 self.hs.config.registration.auto_join_user_id
329 self.hs.config.registration.auto_join_user_id,
330 authenticated_entity=self._server_name,
320331 )
321332
322333 # If the room requires an invite, add the user to the list of invites.
328339 # being necessary this will occur after the invite was sent.
329340 requires_join = True
330341 else:
331 fake_requester = create_requester(user_id)
342 fake_requester = create_requester(
343 user_id, authenticated_entity=self._server_name
344 )
332345
333346 # Choose whether to federate the new room.
334347 if not self.hs.config.registration.autocreate_auto_join_rooms_federated:
361374 # created it, then ensure the first user joins it.
362375 if requires_join:
363376 await room_member_handler.update_membership(
364 requester=create_requester(user_id),
377 requester=create_requester(
378 user_id, authenticated_entity=self._server_name
379 ),
365380 target=UserID.from_string(user_id),
366381 room_id=info["room_id"],
367382 # Since it was just created, there are no remote hosts.
369384 action="join",
370385 ratelimit=False,
371386 )
372
373 except ConsentNotGivenError as e:
374 # Technically not necessary to pull out this error though
375 # moving away from bare excepts is a good thing to do.
376 logger.error("Failed to join new user to %r: %r", r, e)
377387 except Exception as e:
378388 logger.error("Failed to join new user to %r: %r", r, e)
379389
380 async def _join_rooms(self, user_id: str):
390 async def _join_rooms(self, user_id: str) -> None:
381391 """
382392 Join or invite the user to the auto-join rooms.
383393
423433
424434 # Send the invite, if necessary.
425435 if requires_invite:
436 # If an invite is required, there must be a auto-join user ID.
437 assert self.hs.config.registration.auto_join_user_id
438
426439 await room_member_handler.update_membership(
427440 requester=create_requester(
428 self.hs.config.registration.auto_join_user_id
441 self.hs.config.registration.auto_join_user_id,
442 authenticated_entity=self._server_name,
429443 ),
430444 target=UserID.from_string(user_id),
431445 room_id=room_id,
436450
437451 # Send the join.
438452 await room_member_handler.update_membership(
439 requester=create_requester(user_id),
453 requester=create_requester(
454 user_id, authenticated_entity=self._server_name
455 ),
440456 target=UserID.from_string(user_id),
441457 room_id=room_id,
442458 remote_room_hosts=remote_room_hosts,
451467 except Exception as e:
452468 logger.error("Failed to join new user to %r: %r", r, e)
453469
454 async def _auto_join_rooms(self, user_id: str):
470 async def _auto_join_rooms(self, user_id: str) -> None:
455471 """Automatically joins users to auto join rooms - creating the room in the first place
456472 if the user is the first to be created.
457473
474490 else:
475491 await self._join_rooms(user_id)
476492
477 async def post_consent_actions(self, user_id):
493 async def post_consent_actions(self, user_id: str) -> None:
478494 """A series of registration actions that can only be carried out once consent
479495 has been granted
480496
481497 Args:
482 user_id (str): The user to join
498 user_id: The user to join
483499 """
484500 await self._auto_join_rooms(user_id)
485501
486 async def appservice_register(self, user_localpart, as_token):
502 async def appservice_register(self, user_localpart: str, as_token: str) -> str:
487503 user = UserID(user_localpart, self.hs.hostname)
488504 user_id = user.to_string()
489505 service = self.store.get_app_service_by_token(as_token)
508524 )
509525 return user_id
510526
511 def check_user_id_not_appservice_exclusive(self, user_id, allowed_appservice=None):
527 def check_user_id_not_appservice_exclusive(
528 self, user_id: str, allowed_appservice: Optional[ApplicationService] = None
529 ) -> None:
512530 # don't allow people to register the server notices mxid
513531 if self._server_notices_mxid is not None:
514532 if user_id == self._server_notices_mxid:
532550 errcode=Codes.EXCLUSIVE,
533551 )
534552
535 def check_registration_ratelimit(self, address):
553 def check_registration_ratelimit(self, address: Optional[str]) -> None:
536554 """A simple helper method to check whether the registration rate limit has been hit
537555 for a given IP address
538556
539557 Args:
540 address (str|None): the IP address used to perform the registration. If this is
558 address: the IP address used to perform the registration. If this is
541559 None, no ratelimiting will be performed.
542560
543561 Raises:
548566
549567 self.ratelimiter.ratelimit(address)
550568
551 def register_with_store(
569 async def register_with_store(
552570 self,
553 user_id,
554 password_hash=None,
555 was_guest=False,
556 make_guest=False,
557 appservice_id=None,
558 create_profile_with_displayname=None,
559 admin=False,
560 user_type=None,
561 address=None,
562 shadow_banned=False,
563 ):
571 user_id: str,
572 password_hash: Optional[str] = None,
573 was_guest: bool = False,
574 make_guest: bool = False,
575 appservice_id: Optional[str] = None,
576 create_profile_with_displayname: Optional[str] = None,
577 admin: bool = False,
578 user_type: Optional[str] = None,
579 address: Optional[str] = None,
580 shadow_banned: bool = False,
581 ) -> None:
564582 """Register user in the datastore.
565583
566584 Args:
567 user_id (str): The desired user ID to register.
568 password_hash (str|None): Optional. The password hash for this user.
569 was_guest (bool): Optional. Whether this is a guest account being
585 user_id: The desired user ID to register.
586 password_hash: Optional. The password hash for this user.
587 was_guest: Optional. Whether this is a guest account being
570588 upgraded to a non-guest account.
571 make_guest (boolean): True if the the new user should be guest,
589 make_guest: True if the the new user should be guest,
572590 false to add a regular user account.
573 appservice_id (str|None): The ID of the appservice registering the user.
574 create_profile_with_displayname (unicode|None): Optionally create a
591 appservice_id: The ID of the appservice registering the user.
592 create_profile_with_displayname: Optionally create a
575593 profile for the user, setting their displayname to the given value
576 admin (boolean): is an admin user?
577 user_type (str|None): type of user. One of the values from
594 admin: is an admin user?
595 user_type: type of user. One of the values from
578596 api.constants.UserTypes, or None for a normal user.
579 address (str|None): the IP address used to perform the registration.
580 shadow_banned (bool): Whether to shadow-ban the user
581
582 Returns:
583 Awaitable
597 address: the IP address used to perform the registration.
598 shadow_banned: Whether to shadow-ban the user
584599 """
585600 if self.hs.config.worker_app:
586 return self._register_client(
601 await self._register_client(
587602 user_id=user_id,
588603 password_hash=password_hash,
589604 was_guest=was_guest,
596611 shadow_banned=shadow_banned,
597612 )
598613 else:
599 return self.store.register_user(
614 await self.store.register_user(
600615 user_id=user_id,
601616 password_hash=password_hash,
602617 was_guest=was_guest,
609624 )
610625
611626 async def register_device(
612 self, user_id, device_id, initial_display_name, is_guest=False
613 ):
627 self,
628 user_id: str,
629 device_id: Optional[str],
630 initial_display_name: Optional[str],
631 is_guest: bool = False,
632 ) -> Tuple[str, str]:
614633 """Register a device for a user and generate an access token.
615634
616635 The access token will be limited by the homeserver's session_lifetime config.
617636
618637 Args:
619 user_id (str): full canonical @user:id
620 device_id (str|None): The device ID to check, or None to generate
621 a new one.
622 initial_display_name (str|None): An optional display name for the
623 device.
624 is_guest (bool): Whether this is a guest account
638 user_id: full canonical @user:id
639 device_id: The device ID to check, or None to generate a new one.
640 initial_display_name: An optional display name for the device.
641 is_guest: Whether this is a guest account
625642
626643 Returns:
627 tuple[str, str]: Tuple of device ID and access token
644 Tuple of device ID and access token
628645 """
629646
630647 if self.hs.config.worker_app:
644661 )
645662 valid_until_ms = self.clock.time_msec() + self.session_lifetime
646663
647 device_id = await self.device_handler.check_device_registered(
664 registered_device_id = await self.device_handler.check_device_registered(
648665 user_id, device_id, initial_display_name
649666 )
650667 if is_guest:
654671 )
655672 else:
656673 access_token = await self._auth_handler.get_access_token_for_user_id(
657 user_id, device_id=device_id, valid_until_ms=valid_until_ms
658 )
659
660 return (device_id, access_token)
661
662 async def post_registration_actions(self, user_id, auth_result, access_token):
674 user_id, device_id=registered_device_id, valid_until_ms=valid_until_ms
675 )
676
677 return (registered_device_id, access_token)
678
679 async def post_registration_actions(
680 self, user_id: str, auth_result: dict, access_token: Optional[str]
681 ) -> None:
663682 """A user has completed registration
664683
665684 Args:
666 user_id (str): The user ID that consented
667 auth_result (dict): The authenticated credentials of the newly
668 registered user.
669 access_token (str|None): The access token of the newly logged in
670 device, or None if `inhibit_login` enabled.
685 user_id: The user ID that consented
686 auth_result: The authenticated credentials of the newly registered user.
687 access_token: The access token of the newly logged in device, or
688 None if `inhibit_login` enabled.
671689 """
672690 if self.hs.config.worker_app:
673691 await self._post_registration_client(
693711 if auth_result and LoginType.TERMS in auth_result:
694712 await self._on_user_consented(user_id, self.hs.config.user_consent_version)
695713
696 async def _on_user_consented(self, user_id, consent_version):
714 async def _on_user_consented(self, user_id: str, consent_version: str) -> None:
697715 """A user consented to the terms on registration
698716
699717 Args:
700 user_id (str): The user ID that consented.
701 consent_version (str): version of the policy the user has
702 consented to.
718 user_id: The user ID that consented.
719 consent_version: version of the policy the user has consented to.
703720 """
704721 logger.info("%s has consented to the privacy policy", user_id)
705722 await self.store.user_set_consent_version(user_id, consent_version)
706723 await self.post_consent_actions(user_id)
707724
708 async def _register_email_threepid(self, user_id, threepid, token):
725 async def _register_email_threepid(
726 self, user_id: str, threepid: dict, token: Optional[str]
727 ) -> None:
709728 """Add an email address as a 3pid identifier
710729
711730 Also adds an email pusher for the email address, if configured in the
714733 Must be called on master.
715734
716735 Args:
717 user_id (str): id of user
718 threepid (object): m.login.email.identity auth response
719 token (str|None): access_token for the user, or None if not logged
720 in.
736 user_id: id of user
737 threepid: m.login.email.identity auth response
738 token: access_token for the user, or None if not logged in.
721739 """
722740 reqd = ("medium", "address", "validated_at")
723741 if any(x not in threepid for x in reqd):
743761 # up when the access token is saved, but that's quite an
744762 # invasive change I'd rather do separately.
745763 user_tuple = await self.store.get_user_by_access_token(token)
764 # The token better still exist.
765 assert user_tuple
746766 token_id = user_tuple.token_id
747767
748768 await self.pusher_pool.add_pusher(
757777 data={},
758778 )
759779
760 async def _register_msisdn_threepid(self, user_id, threepid):
780 async def _register_msisdn_threepid(self, user_id: str, threepid: dict) -> None:
761781 """Add a phone number as a 3pid identifier
762782
763783 Must be called on master.
764784
765785 Args:
766 user_id (str): id of user
767 threepid (object): m.login.msisdn auth response
786 user_id: id of user
787 threepid: m.login.msisdn auth response
768788 """
769789 try:
770790 assert_params_in_dict(threepid, ["medium", "address", "validated_at"])
586586 """
587587 user_id = requester.user.to_string()
588588
589 await self.auth.check_auth_blocking(user_id)
589 await self.auth.check_auth_blocking(requester=requester)
590590
591591 if (
592592 self._server_notices_mxid is not None
12561256 400, "User must be our own: %s" % (new_room_user_id,)
12571257 )
12581258
1259 room_creator_requester = create_requester(new_room_user_id)
1259 room_creator_requester = create_requester(
1260 new_room_user_id, authenticated_entity=requester_user_id
1261 )
12601262
12611263 info, stream_id = await self._room_creation_handler.create_room(
12621264 room_creator_requester,
12961298
12971299 try:
12981300 # Kick users from room
1299 target_requester = create_requester(user_id)
1301 target_requester = create_requester(
1302 user_id, authenticated_entity=requester_user_id
1303 )
13001304 _, stream_id = await self.room_member_handler.update_membership(
13011305 requester=target_requester,
13021306 target=target_requester.user,
3030 from synapse.api.ratelimiting import Ratelimiter
3131 from synapse.events import EventBase
3232 from synapse.events.snapshot import EventContext
33 from synapse.storage.roommember import RoomsForUser
3433 from synapse.types import JsonDict, Requester, RoomAlias, RoomID, StateMap, UserID
3534 from synapse.util.async_helpers import Linearizer
3635 from synapse.util.distributor import user_left_room
346345 # later on.
347346 content = dict(content)
348347
349 if not self.allow_per_room_profiles or requester.shadow_banned:
348 # allow the server notices mxid to set room-level profile
349 is_requester_server_notices_user = (
350 self._server_notices_mxid is not None
351 and requester.user.to_string() == self._server_notices_mxid
352 )
353
354 if (
355 not self.allow_per_room_profiles and not is_requester_server_notices_user
356 ) or requester.shadow_banned:
350357 # Strip profile data, knowing that new profile data will be added to the
351358 # event's content in event_creation_handler.create_event() using the target's
352359 # global profile.
514521 elif effective_membership_state == Membership.LEAVE:
515522 if not is_host_in_room:
516523 # perhaps we've been invited
517 invite = await self.store.get_invite_for_local_user_in_room(
518 user_id=target.to_string(), room_id=room_id
519 ) # type: Optional[RoomsForUser]
520 if not invite:
524 (
525 current_membership_type,
526 current_membership_event_id,
527 ) = await self.store.get_local_current_membership_for_user_in_room(
528 target.to_string(), room_id
529 )
530 if (
531 current_membership_type != Membership.INVITE
532 or not current_membership_event_id
533 ):
521534 logger.info(
522535 "%s sent a leave request to %s, but that is not an active room "
523536 "on this server, and there is no pending invite",
527540
528541 raise SynapseError(404, "Not a known room")
529542
543 invite = await self.store.get_event(current_membership_event_id)
530544 logger.info(
531545 "%s rejects invite to %s from %s", target, room_id, invite.sender
532546 )
964978
965979 self.distributor = hs.get_distributor()
966980 self.distributor.declare("user_left_room")
981 self._server_name = hs.hostname
967982
968983 async def _is_remote_room_too_complex(
969984 self, room_id: str, remote_room_hosts: List[str]
10581073 return event_id, stream_id
10591074
10601075 # The room is too large. Leave.
1061 requester = types.create_requester(user, None, False, False, None)
1076 requester = types.create_requester(
1077 user, authenticated_entity=self._server_name
1078 )
10621079 await self.update_membership(
10631080 requester=requester, target=user, room_id=room_id, action="leave"
10641081 )
11031120 #
11041121 logger.warning("Failed to reject invite: %s", e)
11051122
1106 return await self._locally_reject_invite(
1123 return await self._generate_local_out_of_band_leave(
11071124 invite_event, txn_id, requester, content
11081125 )
11091126
1110 async def _locally_reject_invite(
1127 async def _generate_local_out_of_band_leave(
11111128 self,
1112 invite_event: EventBase,
1129 previous_membership_event: EventBase,
11131130 txn_id: Optional[str],
11141131 requester: Requester,
11151132 content: JsonDict,
11161133 ) -> Tuple[str, int]:
1117 """Generate a local invite rejection
1118
1119 This is called after we fail to reject an invite via a remote server. It
1120 generates an out-of-band membership event locally.
1134 """Generate a local leave event for a room
1135
1136 This can be called after we e.g fail to reject an invite via a remote server.
1137 It generates an out-of-band membership event locally.
11211138
11221139 Args:
1123 invite_event: the invite to be rejected
1140 previous_membership_event: the previous membership event for this user
11241141 txn_id: optional transaction ID supplied by the client
1125 requester: user making the rejection request, according to the access token
1126 content: additional content to include in the rejection event.
1142 requester: user making the request, according to the access token
1143 content: additional content to include in the leave event.
11271144 Normally an empty dict.
1128 """
1129
1130 room_id = invite_event.room_id
1131 target_user = invite_event.state_key
1145
1146 Returns:
1147 A tuple containing (event_id, stream_id of the leave event)
1148 """
1149 room_id = previous_membership_event.room_id
1150 target_user = previous_membership_event.state_key
11321151
11331152 content["membership"] = Membership.LEAVE
11341153
11401159 "state_key": target_user,
11411160 }
11421161
1143 # the auth events for the new event are the same as that of the invite, plus
1144 # the invite itself.
1162 # the auth events for the new event are the same as that of the previous event, plus
1163 # the event itself.
11451164 #
1146 # the prev_events are just the invite.
1147 prev_event_ids = [invite_event.event_id]
1148 auth_event_ids = invite_event.auth_event_ids() + prev_event_ids
1165 # the prev_events consist solely of the previous membership event.
1166 prev_event_ids = [previous_membership_event.event_id]
1167 auth_event_ids = previous_membership_event.auth_event_ids() + prev_event_ids
11491168
11501169 event, context = await self.event_creation_handler.create_event(
11511170 requester,
2323 from synapse.api.errors import SynapseError
2424 from synapse.config import ConfigError
2525 from synapse.config.saml2_config import SamlAttributeRequirement
26 from synapse.http.server import respond_with_html
26 from synapse.handlers._base import BaseHandler
27 from synapse.handlers.sso import MappingException, UserAttributes
2728 from synapse.http.servlet import parse_string
2829 from synapse.http.site import SynapseRequest
2930 from synapse.module_api import ModuleApi
3637 from synapse.util.iterutils import chunk_seq
3738
3839 if TYPE_CHECKING:
39 import synapse.server
40 from synapse.server import HomeServer
4041
4142 logger = logging.getLogger(__name__)
42
43
44 class MappingException(Exception):
45 """Used to catch errors when mapping the SAML2 response to a user."""
4643
4744
4845 @attr.s(slots=True)
5653 ui_auth_session_id = attr.ib(type=Optional[str], default=None)
5754
5855
59 class SamlHandler:
60 def __init__(self, hs: "synapse.server.HomeServer"):
61 self.hs = hs
56 class SamlHandler(BaseHandler):
57 def __init__(self, hs: "HomeServer"):
58 super().__init__(hs)
6259 self._saml_client = Saml2Client(hs.config.saml2_sp_config)
63 self._auth = hs.get_auth()
60 self._saml_idp_entityid = hs.config.saml2_idp_entityid
6461 self._auth_handler = hs.get_auth_handler()
6562 self._registration_handler = hs.get_registration_handler()
6663
67 self._clock = hs.get_clock()
68 self._datastore = hs.get_datastore()
69 self._hostname = hs.hostname
7064 self._saml2_session_lifetime = hs.config.saml2_session_lifetime
7165 self._grandfathered_mxid_source_attribute = (
7266 hs.config.saml2_grandfathered_mxid_source_attribute
8781 self._outstanding_requests_dict = {} # type: Dict[str, Saml2SessionData]
8882
8983 # a lock on the mappings
90 self._mapping_lock = Linearizer(name="saml_mapping", clock=self._clock)
91
92 def _render_error(
93 self, request, error: str, error_description: Optional[str] = None
94 ) -> None:
95 """Render the error template and respond to the request with it.
96
97 This is used to show errors to the user. The template of this page can
98 be found under `synapse/res/templates/sso_error.html`.
99
100 Args:
101 request: The incoming request from the browser.
102 We'll respond with an HTML page describing the error.
103 error: A technical identifier for this error.
104 error_description: A human-readable description of the error.
105 """
106 html = self._error_template.render(
107 error=error, error_description=error_description
108 )
109 respond_with_html(request, 400, html)
84 self._mapping_lock = Linearizer(name="saml_mapping", clock=self.clock)
85
86 self._sso_handler = hs.get_sso_handler()
11087
11188 def handle_redirect_request(
11289 self, client_redirect_url: bytes, ui_auth_session_id: Optional[str] = None
123100 URL to redirect to
124101 """
125102 reqid, info = self._saml_client.prepare_for_authenticate(
126 relay_state=client_redirect_url
103 entityid=self._saml_idp_entityid, relay_state=client_redirect_url
127104 )
128105
129106 # Since SAML sessions timeout it is useful to log when they were created.
130107 logger.info("Initiating a new SAML session: %s" % (reqid,))
131108
132 now = self._clock.time_msec()
109 now = self.clock.time_msec()
133110 self._outstanding_requests_dict[reqid] = Saml2SessionData(
134111 creation_time=now, ui_auth_session_id=ui_auth_session_id,
135112 )
170147 # in the (user-visible) exception message, so let's log the exception here
171148 # so we can track down the session IDs later.
172149 logger.warning(str(e))
173 self._render_error(
150 self._sso_handler.render_error(
174151 request, "unsolicited_response", "Unexpected SAML2 login."
175152 )
176153 return
177154 except Exception as e:
178 self._render_error(
155 self._sso_handler.render_error(
179156 request,
180157 "invalid_response",
181158 "Unable to parse SAML2 response: %s." % (e,),
183160 return
184161
185162 if saml2_auth.not_signed:
186 self._render_error(
163 self._sso_handler.render_error(
187164 request, "unsigned_respond", "SAML2 response was not signed."
188165 )
189166 return
209186 # attributes.
210187 for requirement in self._saml2_attribute_requirements:
211188 if not _check_attribute_requirement(saml2_auth.ava, requirement):
212 self._render_error(
189 self._sso_handler.render_error(
213190 request, "unauthorised", "You are not authorised to log in here."
214191 )
215192 return
225202 )
226203 except MappingException as e:
227204 logger.exception("Could not map user")
228 self._render_error(request, "mapping_error", str(e))
205 self._sso_handler.render_error(request, "mapping_error", str(e))
229206 return
230207
231208 # Complete the interactive auth session or the login.
271248 "Failed to extract remote user id from SAML response"
272249 )
273250
274 with (await self._mapping_lock.queue(self._auth_provider_id)):
275 # first of all, check if we already have a mapping for this user
276 logger.info(
277 "Looking for existing mapping for user %s:%s",
278 self._auth_provider_id,
279 remote_user_id,
280 )
281 registered_user_id = await self._datastore.get_user_by_external_id(
282 self._auth_provider_id, remote_user_id
283 )
284 if registered_user_id is not None:
285 logger.info("Found existing mapping %s", registered_user_id)
286 return registered_user_id
287
251 async def saml_response_to_remapped_user_attributes(
252 failures: int,
253 ) -> UserAttributes:
254 """
255 Call the mapping provider to map a SAML response to user attributes and coerce the result into the standard form.
256
257 This is backwards compatibility for abstraction for the SSO handler.
258 """
259 # Call the mapping provider.
260 result = self._user_mapping_provider.saml_response_to_user_attributes(
261 saml2_auth, failures, client_redirect_url
262 )
263 # Remap some of the results.
264 return UserAttributes(
265 localpart=result.get("mxid_localpart"),
266 display_name=result.get("displayname"),
267 emails=result.get("emails", []),
268 )
269
270 async def grandfather_existing_users() -> Optional[str]:
288271 # backwards-compatibility hack: see if there is an existing user with a
289272 # suitable mapping from the uid
290273 if (
293276 ):
294277 attrval = saml2_auth.ava[self._grandfathered_mxid_source_attribute][0]
295278 user_id = UserID(
296 map_username_to_mxid_localpart(attrval), self._hostname
279 map_username_to_mxid_localpart(attrval), self.server_name
297280 ).to_string()
298 logger.info(
281
282 logger.debug(
299283 "Looking for existing account based on mapped %s %s",
300284 self._grandfathered_mxid_source_attribute,
301285 user_id,
302286 )
303287
304 users = await self._datastore.get_users_by_id_case_insensitive(user_id)
288 users = await self.store.get_users_by_id_case_insensitive(user_id)
305289 if users:
306290 registered_user_id = list(users.keys())[0]
307291 logger.info("Grandfathering mapping to %s", registered_user_id)
308 await self._datastore.record_user_external_id(
309 self._auth_provider_id, remote_user_id, registered_user_id
310 )
311292 return registered_user_id
312293
313 # Map saml response to user attributes using the configured mapping provider
314 for i in range(1000):
315 attribute_dict = self._user_mapping_provider.saml_response_to_user_attributes(
316 saml2_auth, i, client_redirect_url=client_redirect_url,
317 )
318
319 logger.debug(
320 "Retrieved SAML attributes from user mapping provider: %s "
321 "(attempt %d)",
322 attribute_dict,
323 i,
324 )
325
326 localpart = attribute_dict.get("mxid_localpart")
327 if not localpart:
328 raise MappingException(
329 "Error parsing SAML2 response: SAML mapping provider plugin "
330 "did not return a mxid_localpart value"
331 )
332
333 displayname = attribute_dict.get("displayname")
334 emails = attribute_dict.get("emails", [])
335
336 # Check if this mxid already exists
337 if not await self._datastore.get_users_by_id_case_insensitive(
338 UserID(localpart, self._hostname).to_string()
339 ):
340 # This mxid is free
341 break
342 else:
343 # Unable to generate a username in 1000 iterations
344 # Break and return error to the user
345 raise MappingException(
346 "Unable to generate a Matrix ID from the SAML response"
347 )
348
349 logger.info("Mapped SAML user to local part %s", localpart)
350
351 registered_user_id = await self._registration_handler.register_user(
352 localpart=localpart,
353 default_display_name=displayname,
354 bind_emails=emails,
355 user_agent_ips=(user_agent, ip_address),
356 )
357
358 await self._datastore.record_user_external_id(
359 self._auth_provider_id, remote_user_id, registered_user_id
360 )
361 return registered_user_id
294 return None
295
296 with (await self._mapping_lock.queue(self._auth_provider_id)):
297 return await self._sso_handler.get_mxid_from_sso(
298 self._auth_provider_id,
299 remote_user_id,
300 user_agent,
301 ip_address,
302 saml_response_to_remapped_user_attributes,
303 grandfather_existing_users,
304 )
362305
363306 def expire_sessions(self):
364 expire_before = self._clock.time_msec() - self._saml2_session_lifetime
307 expire_before = self.clock.time_msec() - self._saml2_session_lifetime
365308 to_expire = set()
366309 for reqid, data in self._outstanding_requests_dict.items():
367310 if data.creation_time < expire_before:
473416 )
474417
475418 # Use the configured mapper for this mxid_source
476 base_mxid_localpart = self._mxid_mapper(mxid_source)
419 localpart = self._mxid_mapper(mxid_source)
477420
478421 # Append suffix integer if last call to this function failed to produce
479 # a usable mxid
480 localpart = base_mxid_localpart + (str(failures) if failures else "")
422 # a usable mxid.
423 localpart += str(failures) if failures else ""
481424
482425 # Retrieve the display name from the saml response
483426 # If displayname is None, the mxid_localpart will be used instead
0 # -*- coding: utf-8 -*-
1 # Copyright 2020 The Matrix.org Foundation C.I.C.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 import logging
15 from typing import TYPE_CHECKING, Awaitable, Callable, List, Optional
16
17 import attr
18
19 from synapse.api.errors import RedirectException
20 from synapse.handlers._base import BaseHandler
21 from synapse.http.server import respond_with_html
22 from synapse.types import UserID, contains_invalid_mxid_characters
23
24 if TYPE_CHECKING:
25 from synapse.server import HomeServer
26
27 logger = logging.getLogger(__name__)
28
29
30 class MappingException(Exception):
31 """Used to catch errors when mapping an SSO response to user attributes.
32
33 Note that the msg that is raised is shown to end-users.
34 """
35
36
37 @attr.s
38 class UserAttributes:
39 localpart = attr.ib(type=str)
40 display_name = attr.ib(type=Optional[str], default=None)
41 emails = attr.ib(type=List[str], default=attr.Factory(list))
42
43
44 class SsoHandler(BaseHandler):
45 # The number of attempts to ask the mapping provider for when generating an MXID.
46 _MAP_USERNAME_RETRIES = 1000
47
48 def __init__(self, hs: "HomeServer"):
49 super().__init__(hs)
50 self._registration_handler = hs.get_registration_handler()
51 self._error_template = hs.config.sso_error_template
52
53 def render_error(
54 self, request, error: str, error_description: Optional[str] = None
55 ) -> None:
56 """Renders the error template and responds with it.
57
58 This is used to show errors to the user. The template of this page can
59 be found under `synapse/res/templates/sso_error.html`.
60
61 Args:
62 request: The incoming request from the browser.
63 We'll respond with an HTML page describing the error.
64 error: A technical identifier for this error.
65 error_description: A human-readable description of the error.
66 """
67 html = self._error_template.render(
68 error=error, error_description=error_description
69 )
70 respond_with_html(request, 400, html)
71
72 async def get_sso_user_by_remote_user_id(
73 self, auth_provider_id: str, remote_user_id: str
74 ) -> Optional[str]:
75 """
76 Maps the user ID of a remote IdP to a mxid for a previously seen user.
77
78 If the user has not been seen yet, this will return None.
79
80 Args:
81 auth_provider_id: A unique identifier for this SSO provider, e.g.
82 "oidc" or "saml".
83 remote_user_id: The user ID according to the remote IdP. This might
84 be an e-mail address, a GUID, or some other form. It must be
85 unique and immutable.
86
87 Returns:
88 The mxid of a previously seen user.
89 """
90 logger.debug(
91 "Looking for existing mapping for user %s:%s",
92 auth_provider_id,
93 remote_user_id,
94 )
95
96 # Check if we already have a mapping for this user.
97 previously_registered_user_id = await self.store.get_user_by_external_id(
98 auth_provider_id, remote_user_id,
99 )
100
101 # A match was found, return the user ID.
102 if previously_registered_user_id is not None:
103 logger.info(
104 "Found existing mapping for IdP '%s' and remote_user_id '%s': %s",
105 auth_provider_id,
106 remote_user_id,
107 previously_registered_user_id,
108 )
109 return previously_registered_user_id
110
111 # No match.
112 return None
113
114 async def get_mxid_from_sso(
115 self,
116 auth_provider_id: str,
117 remote_user_id: str,
118 user_agent: str,
119 ip_address: str,
120 sso_to_matrix_id_mapper: Callable[[int], Awaitable[UserAttributes]],
121 grandfather_existing_users: Optional[Callable[[], Awaitable[Optional[str]]]],
122 ) -> str:
123 """
124 Given an SSO ID, retrieve the user ID for it and possibly register the user.
125
126 This first checks if the SSO ID has previously been linked to a matrix ID,
127 if it has that matrix ID is returned regardless of the current mapping
128 logic.
129
130 If a callable is provided for grandfathering users, it is called and can
131 potentially return a matrix ID to use. If it does, the SSO ID is linked to
132 this matrix ID for subsequent calls.
133
134 The mapping function is called (potentially multiple times) to generate
135 a localpart for the user.
136
137 If an unused localpart is generated, the user is registered from the
138 given user-agent and IP address and the SSO ID is linked to this matrix
139 ID for subsequent calls.
140
141 Args:
142 auth_provider_id: A unique identifier for this SSO provider, e.g.
143 "oidc" or "saml".
144 remote_user_id: The unique identifier from the SSO provider.
145 user_agent: The user agent of the client making the request.
146 ip_address: The IP address of the client making the request.
147 sso_to_matrix_id_mapper: A callable to generate the user attributes.
148 The only parameter is an integer which represents the amount of
149 times the returned mxid localpart mapping has failed.
150
151 It is expected that the mapper can raise two exceptions, which
152 will get passed through to the caller:
153
154 MappingException if there was a problem mapping the response
155 to the user.
156 RedirectException to redirect to an additional page (e.g.
157 to prompt the user for more information).
158 grandfather_existing_users: A callable which can return an previously
159 existing matrix ID. The SSO ID is then linked to the returned
160 matrix ID.
161
162 Returns:
163 The user ID associated with the SSO response.
164
165 Raises:
166 MappingException if there was a problem mapping the response to a user.
167 RedirectException: if the mapping provider needs to redirect the user
168 to an additional page. (e.g. to prompt for more information)
169
170 """
171 # first of all, check if we already have a mapping for this user
172 previously_registered_user_id = await self.get_sso_user_by_remote_user_id(
173 auth_provider_id, remote_user_id,
174 )
175 if previously_registered_user_id:
176 return previously_registered_user_id
177
178 # Check for grandfathering of users.
179 if grandfather_existing_users:
180 previously_registered_user_id = await grandfather_existing_users()
181 if previously_registered_user_id:
182 # Future logins should also match this user ID.
183 await self.store.record_user_external_id(
184 auth_provider_id, remote_user_id, previously_registered_user_id
185 )
186 return previously_registered_user_id
187
188 # Otherwise, generate a new user.
189 for i in range(self._MAP_USERNAME_RETRIES):
190 try:
191 attributes = await sso_to_matrix_id_mapper(i)
192 except (RedirectException, MappingException):
193 # Mapping providers are allowed to issue a redirect (e.g. to ask
194 # the user for more information) and can issue a mapping exception
195 # if a name cannot be generated.
196 raise
197 except Exception as e:
198 # Any other exception is unexpected.
199 raise MappingException(
200 "Could not extract user attributes from SSO response."
201 ) from e
202
203 logger.debug(
204 "Retrieved user attributes from user mapping provider: %r (attempt %d)",
205 attributes,
206 i,
207 )
208
209 if not attributes.localpart:
210 raise MappingException(
211 "Error parsing SSO response: SSO mapping provider plugin "
212 "did not return a localpart value"
213 )
214
215 # Check if this mxid already exists
216 user_id = UserID(attributes.localpart, self.server_name).to_string()
217 if not await self.store.get_users_by_id_case_insensitive(user_id):
218 # This mxid is free
219 break
220 else:
221 # Unable to generate a username in 1000 iterations
222 # Break and return error to the user
223 raise MappingException(
224 "Unable to generate a Matrix ID from the SSO response"
225 )
226
227 # Since the localpart is provided via a potentially untrusted module,
228 # ensure the MXID is valid before registering.
229 if contains_invalid_mxid_characters(attributes.localpart):
230 raise MappingException("localpart is invalid: %s" % (attributes.localpart,))
231
232 logger.debug("Mapped SSO user to local part %s", attributes.localpart)
233 registered_user_id = await self._registration_handler.register_user(
234 localpart=attributes.localpart,
235 default_display_name=attributes.display_name,
236 bind_emails=attributes.emails,
237 user_agent_ips=[(user_agent, ip_address)],
238 )
239
240 await self.store.record_user_external_id(
241 auth_provider_id, remote_user_id, registered_user_id
242 )
243 return registered_user_id
3030 Collection,
3131 JsonDict,
3232 MutableStateMap,
33 Requester,
3334 RoomStreamToken,
3435 StateMap,
3536 StreamToken,
259260
260261 async def wait_for_sync_for_user(
261262 self,
263 requester: Requester,
262264 sync_config: SyncConfig,
263265 since_token: Optional[StreamToken] = None,
264266 timeout: int = 0,
272274 # not been exceeded (if not part of the group by this point, almost certain
273275 # auth_blocking will occur)
274276 user_id = sync_config.user.to_string()
275 await self.auth.check_auth_blocking(user_id)
277 await self.auth.check_auth_blocking(requester=requester)
276278
277279 res = await self.response_cache.wrap(
278280 sync_config.request_key,
1313 # See the License for the specific language governing permissions and
1414 # limitations under the License.
1515 import logging
16 import urllib
16 import urllib.parse
1717 from io import BytesIO
1818 from typing import (
19 TYPE_CHECKING,
1920 Any,
2021 BinaryIO,
2122 Dict,
3031
3132 import treq
3233 from canonicaljson import encode_canonical_json
33 from netaddr import IPAddress
34 from netaddr import IPAddress, IPSet
3435 from prometheus_client import Counter
3536 from zope.interface import implementer, provider
3637
3839 from OpenSSL.SSL import VERIFY_NONE
3940 from twisted.internet import defer, error as twisted_error, protocol, ssl
4041 from twisted.internet.interfaces import (
42 IAddress,
43 IHostResolution,
4144 IReactorPluggableNameResolver,
4245 IResolutionReceiver,
4346 )
5255 )
5356 from twisted.web.http import PotentialDataLoss
5457 from twisted.web.http_headers import Headers
55 from twisted.web.iweb import IResponse
58 from twisted.web.iweb import IAgent, IBodyProducer, IResponse
5659
5760 from synapse.api.errors import Codes, HttpResponseException, SynapseError
5861 from synapse.http import QuieterFileBodyProducer, RequestTimedOutError, redact_uri
6265 from synapse.util import json_decoder
6366 from synapse.util.async_helpers import timeout_deferred
6467
68 if TYPE_CHECKING:
69 from synapse.app.homeserver import HomeServer
70
6571 logger = logging.getLogger(__name__)
6672
6773 outgoing_requests_counter = Counter("synapse_http_client_requests", "", ["method"])
8389 QueryParams = Union[Mapping[str, QueryParamValue], Mapping[bytes, QueryParamValue]]
8490
8591
86 def check_against_blacklist(ip_address, ip_whitelist, ip_blacklist):
87 """
92 def check_against_blacklist(
93 ip_address: IPAddress, ip_whitelist: Optional[IPSet], ip_blacklist: IPSet
94 ) -> bool:
95 """
96 Compares an IP address to allowed and disallowed IP sets.
97
8898 Args:
89 ip_address (netaddr.IPAddress)
90 ip_whitelist (netaddr.IPSet)
91 ip_blacklist (netaddr.IPSet)
99 ip_address: The IP address to check
100 ip_whitelist: Allowed IP addresses.
101 ip_blacklist: Disallowed IP addresses.
102
103 Returns:
104 True if the IP address is in the blacklist and not in the whitelist.
92105 """
93106 if ip_address in ip_blacklist:
94107 if ip_whitelist is None or ip_address not in ip_whitelist:
117130 addresses, preventing DNS rebinding attacks on URL preview.
118131 """
119132
120 def __init__(self, reactor, ip_whitelist, ip_blacklist):
121 """
122 Args:
123 reactor (twisted.internet.reactor)
124 ip_whitelist (netaddr.IPSet)
125 ip_blacklist (netaddr.IPSet)
133 def __init__(
134 self,
135 reactor: IReactorPluggableNameResolver,
136 ip_whitelist: Optional[IPSet],
137 ip_blacklist: IPSet,
138 ):
139 """
140 Args:
141 reactor: The twisted reactor.
142 ip_whitelist: IP addresses to allow.
143 ip_blacklist: IP addresses to disallow.
126144 """
127145 self._reactor = reactor
128146 self._ip_whitelist = ip_whitelist
129147 self._ip_blacklist = ip_blacklist
130148
131 def resolveHostName(self, recv, hostname, portNumber=0):
149 def resolveHostName(
150 self, recv: IResolutionReceiver, hostname: str, portNumber: int = 0
151 ) -> IResolutionReceiver:
132152
133153 r = recv()
134 addresses = []
135
136 def _callback():
154 addresses = [] # type: List[IAddress]
155
156 def _callback() -> None:
137157 r.resolutionBegan(None)
138158
139159 has_bad_ip = False
160180 @provider(IResolutionReceiver)
161181 class EndpointReceiver:
162182 @staticmethod
163 def resolutionBegan(resolutionInProgress):
183 def resolutionBegan(resolutionInProgress: IHostResolution) -> None:
164184 pass
165185
166186 @staticmethod
167 def addressResolved(address):
187 def addressResolved(address: IAddress) -> None:
168188 addresses.append(address)
169189
170190 @staticmethod
171 def resolutionComplete():
191 def resolutionComplete() -> None:
172192 _callback()
173193
174194 self._reactor.nameResolver.resolveHostName(
184204 directly (without an IP address lookup).
185205 """
186206
187 def __init__(self, agent, reactor, ip_whitelist=None, ip_blacklist=None):
188 """
189 Args:
190 agent (twisted.web.client.Agent): The Agent to wrap.
191 reactor (twisted.internet.reactor)
192 ip_whitelist (netaddr.IPSet)
193 ip_blacklist (netaddr.IPSet)
207 def __init__(
208 self,
209 agent: IAgent,
210 ip_whitelist: Optional[IPSet] = None,
211 ip_blacklist: Optional[IPSet] = None,
212 ):
213 """
214 Args:
215 agent: The Agent to wrap.
216 ip_whitelist: IP addresses to allow.
217 ip_blacklist: IP addresses to disallow.
194218 """
195219 self._agent = agent
196220 self._ip_whitelist = ip_whitelist
197221 self._ip_blacklist = ip_blacklist
198222
199 def request(self, method, uri, headers=None, bodyProducer=None):
223 def request(
224 self,
225 method: bytes,
226 uri: bytes,
227 headers: Optional[Headers] = None,
228 bodyProducer: Optional[IBodyProducer] = None,
229 ) -> defer.Deferred:
200230 h = urllib.parse.urlparse(uri.decode("ascii"))
201231
202232 try:
225255
226256 def __init__(
227257 self,
228 hs,
229 treq_args={},
230 ip_whitelist=None,
231 ip_blacklist=None,
232 http_proxy=None,
233 https_proxy=None,
258 hs: "HomeServer",
259 treq_args: Dict[str, Any] = {},
260 ip_whitelist: Optional[IPSet] = None,
261 ip_blacklist: Optional[IPSet] = None,
262 http_proxy: Optional[bytes] = None,
263 https_proxy: Optional[bytes] = None,
234264 ):
235265 """
236266 Args:
237 hs (synapse.server.HomeServer)
238 treq_args (dict): Extra keyword arguments to be given to treq.request.
239 ip_blacklist (netaddr.IPSet): The IP addresses that are blacklisted that
267 hs
268 treq_args: Extra keyword arguments to be given to treq.request.
269 ip_blacklist: The IP addresses that are blacklisted that
240270 we may not request.
241 ip_whitelist (netaddr.IPSet): The whitelisted IP addresses, that we can
271 ip_whitelist: The whitelisted IP addresses, that we can
242272 request if it were otherwise caught in a blacklist.
243 http_proxy (bytes): proxy server to use for http connections. host[:port]
244 https_proxy (bytes): proxy server to use for https connections. host[:port]
273 http_proxy: proxy server to use for http connections. host[:port]
274 https_proxy: proxy server to use for https connections. host[:port]
245275 """
246276 self.hs = hs
247277
305335 # by the DNS resolution.
306336 self.agent = BlacklistingAgentWrapper(
307337 self.agent,
308 self.reactor,
309338 ip_whitelist=self._ip_whitelist,
310339 ip_blacklist=self._ip_blacklist,
311340 )
396425 async def post_urlencoded_get_json(
397426 self,
398427 uri: str,
399 args: Mapping[str, Union[str, List[str]]] = {},
428 args: Optional[Mapping[str, Union[str, List[str]]]] = None,
400429 headers: Optional[RawHeaders] = None,
401430 ) -> Any:
402431 """
421450 # TODO: Do we ever want to log message contents?
422451 logger.debug("post_urlencoded_get_json args: %s", args)
423452
424 query_bytes = urllib.parse.urlencode(encode_urlencode_args(args), True).encode(
425 "utf8"
426 )
453 query_bytes = encode_query_args(args)
427454
428455 actual_headers = {
429456 b"Content-Type": [b"application/x-www-form-urlencoded"],
431458 b"Accept": [b"application/json"],
432459 }
433460 if headers:
434 actual_headers.update(headers)
461 actual_headers.update(headers) # type: ignore
435462
436463 response = await self.request(
437464 "POST", uri, headers=Headers(actual_headers), data=query_bytes
478505 b"Accept": [b"application/json"],
479506 }
480507 if headers:
481 actual_headers.update(headers)
508 actual_headers.update(headers) # type: ignore
482509
483510 response = await self.request(
484511 "POST", uri, headers=Headers(actual_headers), data=json_str
494521 )
495522
496523 async def get_json(
497 self, uri: str, args: QueryParams = {}, headers: Optional[RawHeaders] = None,
524 self,
525 uri: str,
526 args: Optional[QueryParams] = None,
527 headers: Optional[RawHeaders] = None,
498528 ) -> Any:
499529 """Gets some json from the given URI.
500530
515545 """
516546 actual_headers = {b"Accept": [b"application/json"]}
517547 if headers:
518 actual_headers.update(headers)
548 actual_headers.update(headers) # type: ignore
519549
520550 body = await self.get_raw(uri, args, headers=headers)
521551 return json_decoder.decode(body.decode("utf-8"))
524554 self,
525555 uri: str,
526556 json_body: Any,
527 args: QueryParams = {},
557 args: Optional[QueryParams] = None,
528558 headers: RawHeaders = None,
529559 ) -> Any:
530560 """Puts some json to the given URI.
545575
546576 ValueError: if the response was not JSON
547577 """
548 if len(args):
549 query_bytes = urllib.parse.urlencode(args, True)
550 uri = "%s?%s" % (uri, query_bytes)
578 if args:
579 query_str = urllib.parse.urlencode(args, True)
580 uri = "%s?%s" % (uri, query_str)
551581
552582 json_str = encode_canonical_json(json_body)
553583
557587 b"Accept": [b"application/json"],
558588 }
559589 if headers:
560 actual_headers.update(headers)
590 actual_headers.update(headers) # type: ignore
561591
562592 response = await self.request(
563593 "PUT", uri, headers=Headers(actual_headers), data=json_str
573603 )
574604
575605 async def get_raw(
576 self, uri: str, args: QueryParams = {}, headers: Optional[RawHeaders] = None
606 self,
607 uri: str,
608 args: Optional[QueryParams] = None,
609 headers: Optional[RawHeaders] = None,
577610 ) -> bytes:
578611 """Gets raw text from the given URI.
579612
591624
592625 HttpResponseException on a non-2xx HTTP response.
593626 """
594 if len(args):
595 query_bytes = urllib.parse.urlencode(args, True)
596 uri = "%s?%s" % (uri, query_bytes)
627 if args:
628 query_str = urllib.parse.urlencode(args, True)
629 uri = "%s?%s" % (uri, query_str)
597630
598631 actual_headers = {b"User-Agent": [self.user_agent]}
599632 if headers:
600 actual_headers.update(headers)
633 actual_headers.update(headers) # type: ignore
601634
602635 response = await self.request("GET", uri, headers=Headers(actual_headers))
603636
640673
641674 actual_headers = {b"User-Agent": [self.user_agent]}
642675 if headers:
643 actual_headers.update(headers)
676 actual_headers.update(headers) # type: ignore
644677
645678 response = await self.request("GET", url, headers=Headers(actual_headers))
646679
648681
649682 if (
650683 b"Content-Length" in resp_headers
684 and max_size
651685 and int(resp_headers[b"Content-Length"][0]) > max_size
652686 ):
653 logger.warning("Requested URL is too large > %r bytes" % (self.max_size,))
687 logger.warning("Requested URL is too large > %r bytes" % (max_size,))
654688 raise SynapseError(
655689 502,
656 "Requested file is too large > %r bytes" % (self.max_size,),
690 "Requested file is too large > %r bytes" % (max_size,),
657691 Codes.TOO_LARGE,
658692 )
659693
667701
668702 try:
669703 length = await make_deferred_yieldable(
670 _readBodyToFile(response, output_stream, max_size)
704 readBodyToFile(response, output_stream, max_size)
671705 )
672706 except SynapseError:
673707 # This can happen e.g. because the body is too large.
695729 return f
696730
697731
698 # XXX: FIXME: This is horribly copy-pasted from matrixfederationclient.
699 # The two should be factored out.
700
701
702732 class _ReadBodyToFileProtocol(protocol.Protocol):
703 def __init__(self, stream, deferred, max_size):
733 def __init__(
734 self, stream: BinaryIO, deferred: defer.Deferred, max_size: Optional[int]
735 ):
704736 self.stream = stream
705737 self.deferred = deferred
706738 self.length = 0
707739 self.max_size = max_size
708740
709 def dataReceived(self, data):
741 def dataReceived(self, data: bytes) -> None:
710742 self.stream.write(data)
711743 self.length += len(data)
712744 if self.max_size is not None and self.length >= self.max_size:
720752 self.deferred = defer.Deferred()
721753 self.transport.loseConnection()
722754
723 def connectionLost(self, reason):
755 def connectionLost(self, reason: Failure) -> None:
724756 if reason.check(ResponseDone):
725757 self.deferred.callback(self.length)
726758 elif reason.check(PotentialDataLoss):
731763 self.deferred.errback(reason)
732764
733765
734 # XXX: FIXME: This is horribly copy-pasted from matrixfederationclient.
735 # The two should be factored out.
736
737
738 def _readBodyToFile(response, stream, max_size):
766 def readBodyToFile(
767 response: IResponse, stream: BinaryIO, max_size: Optional[int]
768 ) -> defer.Deferred:
769 """
770 Read a HTTP response body to a file-object. Optionally enforcing a maximum file size.
771
772 Args:
773 response: The HTTP response to read from.
774 stream: The file-object to write to.
775 max_size: The maximum file size to allow.
776
777 Returns:
778 A Deferred which resolves to the length of the read body.
779 """
780
739781 d = defer.Deferred()
740782 response.deliverBody(_ReadBodyToFileProtocol(stream, d, max_size))
741783 return d
742784
743785
744 def encode_urlencode_args(args):
745 return {k: encode_urlencode_arg(v) for k, v in args.items()}
746
747
748 def encode_urlencode_arg(arg):
749 if isinstance(arg, str):
750 return arg.encode("utf-8")
751 elif isinstance(arg, list):
752 return [encode_urlencode_arg(i) for i in arg]
753 else:
754 return arg
755
756
757 def _print_ex(e):
758 if hasattr(e, "reasons") and e.reasons:
759 for ex in e.reasons:
760 _print_ex(ex)
761 else:
762 logger.exception(e)
786 def encode_query_args(args: Optional[Mapping[str, Union[str, List[str]]]]) -> bytes:
787 """
788 Encodes a map of query arguments to bytes which can be appended to a URL.
789
790 Args:
791 args: The query arguments, a mapping of string to string or list of strings.
792
793 Returns:
794 The query arguments encoded as bytes.
795 """
796 if args is None:
797 return b""
798
799 encoded_args = {}
800 for k, vs in args.items():
801 if isinstance(vs, str):
802 vs = [vs]
803 encoded_args[k] = [v.encode("utf8") for v in vs]
804
805 query_str = urllib.parse.urlencode(encoded_args, True)
806
807 return query_str.encode("utf8")
763808
764809
765810 class InsecureInterceptableContextFactory(ssl.ContextFactory):
1111 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212 # See the License for the specific language governing permissions and
1313 # limitations under the License.
14
1514 import logging
16 import urllib
17 from typing import List
15 import urllib.parse
16 from typing import List, Optional
1817
1918 from netaddr import AddrFormatError, IPAddress
2019 from zope.interface import implementer
2120
2221 from twisted.internet import defer
2322 from twisted.internet.endpoints import HostnameEndpoint, wrapClientTLS
24 from twisted.internet.interfaces import IStreamClientEndpoint
25 from twisted.web.client import Agent, HTTPConnectionPool
23 from twisted.internet.interfaces import (
24 IProtocolFactory,
25 IReactorCore,
26 IStreamClientEndpoint,
27 )
28 from twisted.web.client import URI, Agent, HTTPConnectionPool
2629 from twisted.web.http_headers import Headers
27 from twisted.web.iweb import IAgent, IAgentEndpointFactory
28
30 from twisted.web.iweb import IAgent, IAgentEndpointFactory, IBodyProducer
31
32 from synapse.crypto.context_factory import FederationPolicyForHTTPS
2933 from synapse.http.federation.srv_resolver import Server, SrvResolver
3034 from synapse.http.federation.well_known_resolver import WellKnownResolver
3135 from synapse.logging.context import make_deferred_yieldable, run_in_background
4347 Doesn't implement any retries. (Those are done in MatrixFederationHttpClient.)
4448
4549 Args:
46 reactor (IReactor): twisted reactor to use for underlying requests
47
48 tls_client_options_factory (FederationPolicyForHTTPS|None):
50 reactor: twisted reactor to use for underlying requests
51
52 tls_client_options_factory:
4953 factory to use for fetching client tls options, or none to disable TLS.
5054
51 user_agent (bytes):
55 user_agent:
5256 The user agent header to use for federation requests.
5357
54 _srv_resolver (SrvResolver|None):
55 SRVResolver impl to use for looking up SRV records. None to use a default
56 implementation.
57
58 _well_known_resolver (WellKnownResolver|None):
58 _srv_resolver:
59 SrvResolver implementation to use for looking up SRV records. None
60 to use a default implementation.
61
62 _well_known_resolver:
5963 WellKnownResolver to use to perform well-known lookups. None to use a
6064 default implementation.
6165 """
6266
6367 def __init__(
6468 self,
65 reactor,
66 tls_client_options_factory,
67 user_agent,
68 _srv_resolver=None,
69 _well_known_resolver=None,
69 reactor: IReactorCore,
70 tls_client_options_factory: Optional[FederationPolicyForHTTPS],
71 user_agent: bytes,
72 _srv_resolver: Optional[SrvResolver] = None,
73 _well_known_resolver: Optional[WellKnownResolver] = None,
7074 ):
7175 self._reactor = reactor
7276 self._clock = Clock(reactor)
98102 self._well_known_resolver = _well_known_resolver
99103
100104 @defer.inlineCallbacks
101 def request(self, method, uri, headers=None, bodyProducer=None):
105 def request(
106 self,
107 method: bytes,
108 uri: bytes,
109 headers: Optional[Headers] = None,
110 bodyProducer: Optional[IBodyProducer] = None,
111 ) -> defer.Deferred:
102112 """
103113 Args:
104 method (bytes): HTTP method: GET/POST/etc
105 uri (bytes): Absolute URI to be retrieved
106 headers (twisted.web.http_headers.Headers|None):
107 HTTP headers to send with the request, or None to
108 send no extra headers.
109 bodyProducer (twisted.web.iweb.IBodyProducer|None):
114 method: HTTP method: GET/POST/etc
115 uri: Absolute URI to be retrieved
116 headers:
117 HTTP headers to send with the request, or None to send no extra headers.
118 bodyProducer:
110119 An object which can generate bytes to make up the
111120 body of this request (for example, the properly encoded contents of
112121 a file for a file upload). Or None if the request is to have
121130 # We use urlparse as that will set `port` to None if there is no
122131 # explicit port.
123132 parsed_uri = urllib.parse.urlparse(uri)
133
134 # There must be a valid hostname.
135 assert parsed_uri.hostname
124136
125137 # If this is a matrix:// URI check if the server has delegated matrix
126138 # traffic using well-known delegation.
178190 """Factory for MatrixHostnameEndpoint for parsing to an Agent.
179191 """
180192
181 def __init__(self, reactor, tls_client_options_factory, srv_resolver):
193 def __init__(
194 self,
195 reactor: IReactorCore,
196 tls_client_options_factory: Optional[FederationPolicyForHTTPS],
197 srv_resolver: Optional[SrvResolver],
198 ):
182199 self._reactor = reactor
183200 self._tls_client_options_factory = tls_client_options_factory
184201
202219 resolution (i.e. via SRV). Does not check for well-known delegation.
203220
204221 Args:
205 reactor (IReactor)
206 tls_client_options_factory (ClientTLSOptionsFactory|None):
222 reactor: twisted reactor to use for underlying requests
223 tls_client_options_factory:
207224 factory to use for fetching client tls options, or none to disable TLS.
208 srv_resolver (SrvResolver): The SRV resolver to use
209 parsed_uri (twisted.web.client.URI): The parsed URI that we're wanting
210 to connect to.
225 srv_resolver: The SRV resolver to use
226 parsed_uri: The parsed URI that we're wanting to connect to.
211227 """
212228
213 def __init__(self, reactor, tls_client_options_factory, srv_resolver, parsed_uri):
229 def __init__(
230 self,
231 reactor: IReactorCore,
232 tls_client_options_factory: Optional[FederationPolicyForHTTPS],
233 srv_resolver: SrvResolver,
234 parsed_uri: URI,
235 ):
214236 self._reactor = reactor
215237
216238 self._parsed_uri = parsed_uri
230252
231253 self._srv_resolver = srv_resolver
232254
233 def connect(self, protocol_factory):
255 def connect(self, protocol_factory: IProtocolFactory) -> defer.Deferred:
234256 """Implements IStreamClientEndpoint interface
235257 """
236258
237259 return run_in_background(self._do_connect, protocol_factory)
238260
239 async def _do_connect(self, protocol_factory):
261 async def _do_connect(self, protocol_factory: IProtocolFactory) -> None:
240262 first_exception = None
241263
242264 server_list = await self._resolve_server()
302324 return [Server(host, 8448)]
303325
304326
305 def _is_ip_literal(host):
327 def _is_ip_literal(host: bytes) -> bool:
306328 """Test if the given host name is either an IPv4 or IPv6 literal.
307329
308330 Args:
309 host (bytes)
331 host: The host name to check
310332
311333 Returns:
312 bool
334 True if the hostname is an IP address literal.
313335 """
314336
315 host = host.decode("ascii")
337 host_str = host.decode("ascii")
316338
317339 try:
318 IPAddress(host)
340 IPAddress(host_str)
319341 return True
320342 except AddrFormatError:
321343 return False
1111 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212 # See the License for the specific language governing permissions and
1313 # limitations under the License.
14
1514 import logging
1615 import random
1716 import time
2019 import attr
2120
2221 from twisted.internet import defer
22 from twisted.internet.interfaces import IReactorTime
2323 from twisted.web.client import RedirectAgent, readBody
2424 from twisted.web.http import stringToDatetime
2525 from twisted.web.http_headers import Headers
26 from twisted.web.iweb import IResponse
26 from twisted.web.iweb import IAgent, IResponse
2727
2828 from synapse.logging.context import make_deferred_yieldable
2929 from synapse.util import Clock, json_decoder
8080
8181 def __init__(
8282 self,
83 reactor,
84 agent,
85 user_agent,
86 well_known_cache=None,
87 had_well_known_cache=None,
83 reactor: IReactorTime,
84 agent: IAgent,
85 user_agent: bytes,
86 well_known_cache: Optional[TTLCache] = None,
87 had_well_known_cache: Optional[TTLCache] = None,
8888 ):
8989 self._reactor = reactor
9090 self._clock = Clock(reactor)
126126 with Measure(self._clock, "get_well_known"):
127127 result, cache_period = await self._fetch_well_known(
128128 server_name
129 ) # type: Tuple[Optional[bytes], float]
129 ) # type: Optional[bytes], float
130130
131131 except _FetchWellKnownFailure as e:
132132 if prev_result and e.temporary:
1616 import logging
1717 import random
1818 import sys
19 import urllib
19 import urllib.parse
2020 from io import BytesIO
21 from typing import Callable, Dict, List, Optional, Tuple, Union
2122
2223 import attr
2324 import treq
2627 from signedjson.sign import sign_json
2728 from zope.interface import implementer
2829
29 from twisted.internet import defer, protocol
30 from twisted.internet import defer
3031 from twisted.internet.error import DNSLookupError
3132 from twisted.internet.interfaces import IReactorPluggableNameResolver, IReactorTime
3233 from twisted.internet.task import _EPSILON, Cooperator
33 from twisted.web._newclient import ResponseDone
3434 from twisted.web.http_headers import Headers
35 from twisted.web.iweb import IResponse
35 from twisted.web.iweb import IBodyProducer, IResponse
3636
3737 import synapse.metrics
3838 import synapse.util.retryutils
3939 from synapse.api.errors import (
40 Codes,
4140 FederationDeniedError,
4241 HttpResponseException,
4342 RequestSendFailed,
44 SynapseError,
4543 )
4644 from synapse.http import QuieterFileBodyProducer
47 from synapse.http.client import BlacklistingAgentWrapper, IPBlacklistingResolver
45 from synapse.http.client import (
46 BlacklistingAgentWrapper,
47 IPBlacklistingResolver,
48 encode_query_args,
49 readBodyToFile,
50 )
4851 from synapse.http.federation.matrix_federation_agent import MatrixFederationAgent
4952 from synapse.logging.context import make_deferred_yieldable
5053 from synapse.logging.opentracing import (
5356 start_active_span,
5457 tags,
5558 )
59 from synapse.types import JsonDict
5660 from synapse.util import json_decoder
5761 from synapse.util.async_helpers import timeout_deferred
5862 from synapse.util.metrics import Measure
7579 _next_id = 1
7680
7781
82 QueryArgs = Dict[str, Union[str, List[str]]]
83
84
7885 @attr.s(slots=True, frozen=True)
7986 class MatrixFederationRequest:
80 method = attr.ib()
87 method = attr.ib(type=str)
8188 """HTTP method
82 :type: str
83 """
84
85 path = attr.ib()
89 """
90
91 path = attr.ib(type=str)
8692 """HTTP path
87 :type: str
88 """
89
90 destination = attr.ib()
93 """
94
95 destination = attr.ib(type=str)
9196 """The remote server to send the HTTP request to.
92 :type: str"""
93
94 json = attr.ib(default=None)
97 """
98
99 json = attr.ib(default=None, type=Optional[JsonDict])
95100 """JSON to send in the body.
96 :type: dict|None
97 """
98
99 json_callback = attr.ib(default=None)
101 """
102
103 json_callback = attr.ib(default=None, type=Optional[Callable[[], JsonDict]])
100104 """A callback to generate the JSON.
101 :type: func|None
102 """
103
104 query = attr.ib(default=None)
105 """
106
107 query = attr.ib(default=None, type=Optional[dict])
105108 """Query arguments.
106 :type: dict|None
107 """
108
109 txn_id = attr.ib(default=None)
109 """
110
111 txn_id = attr.ib(default=None, type=Optional[str])
110112 """Unique ID for this request (for logging)
111 :type: str|None
112113 """
113114
114115 uri = attr.ib(init=False, type=bytes)
115116 """The URI of this request
116117 """
117118
118 def __attrs_post_init__(self):
119 def __attrs_post_init__(self) -> None:
119120 global _next_id
120121 txn_id = "%s-O-%s" % (self.method, _next_id)
121122 _next_id = (_next_id + 1) % (MAXINT - 1)
135136 )
136137 object.__setattr__(self, "uri", uri)
137138
138 def get_json(self):
139 def get_json(self) -> Optional[JsonDict]:
139140 if self.json_callback:
140141 return self.json_callback()
141142 return self.json
147148 request: MatrixFederationRequest,
148149 response: IResponse,
149150 start_ms: int,
150 ):
151 ) -> JsonDict:
151152 """
152153 Reads the JSON body of a response, with a timeout
153154
159160 start_ms: Timestamp when request was made
160161
161162 Returns:
162 dict: parsed JSON response
163 The parsed JSON response
163164 """
164165 try:
165166 check_content_type_is_json(response.headers)
249250 # Use a BlacklistingAgentWrapper to prevent circumventing the IP
250251 # blacklist via IP literals in server names
251252 self.agent = BlacklistingAgentWrapper(
252 self.agent,
253 self.reactor,
254 ip_blacklist=hs.config.federation_ip_range_blacklist,
253 self.agent, ip_blacklist=hs.config.federation_ip_range_blacklist,
255254 )
256255
257256 self.clock = hs.get_clock()
265264 self._cooperator = Cooperator(scheduler=schedule)
266265
267266 async def _send_request_with_optional_trailing_slash(
268 self, request, try_trailing_slash_on_400=False, **send_request_args
269 ):
267 self,
268 request: MatrixFederationRequest,
269 try_trailing_slash_on_400: bool = False,
270 **send_request_args
271 ) -> IResponse:
270272 """Wrapper for _send_request which can optionally retry the request
271273 upon receiving a combination of a 400 HTTP response code and a
272274 'M_UNRECOGNIZED' errcode. This is a workaround for Synapse <= v0.99.3
273275 due to #3622.
274276
275277 Args:
276 request (MatrixFederationRequest): details of request to be sent
277 try_trailing_slash_on_400 (bool): Whether on receiving a 400
278 request: details of request to be sent
279 try_trailing_slash_on_400: Whether on receiving a 400
278280 'M_UNRECOGNIZED' from the server to retry the request with a
279281 trailing slash appended to the request path.
280 send_request_args (Dict): A dictionary of arguments to pass to
281 `_send_request()`.
282 send_request_args: A dictionary of arguments to pass to `_send_request()`.
282283
283284 Raises:
284285 HttpResponseException: If we get an HTTP response code >= 300
285286 (except 429).
286287
287288 Returns:
288 Dict: Parsed JSON response body.
289 Parsed JSON response body.
289290 """
290291 try:
291292 response = await self._send_request(request, **send_request_args)
312313
313314 async def _send_request(
314315 self,
315 request,
316 retry_on_dns_fail=True,
317 timeout=None,
318 long_retries=False,
319 ignore_backoff=False,
320 backoff_on_404=False,
321 ):
316 request: MatrixFederationRequest,
317 retry_on_dns_fail: bool = True,
318 timeout: Optional[int] = None,
319 long_retries: bool = False,
320 ignore_backoff: bool = False,
321 backoff_on_404: bool = False,
322 ) -> IResponse:
322323 """
323324 Sends a request to the given server.
324325
325326 Args:
326 request (MatrixFederationRequest): details of request to be sent
327
328 timeout (int|None): number of milliseconds to wait for the response headers
327 request: details of request to be sent
328
329 retry_on_dns_fail: true if the request should be retied on DNS failures
330
331 timeout: number of milliseconds to wait for the response headers
329332 (including connecting to the server), *for each attempt*.
330333 60s by default.
331334
332 long_retries (bool): whether to use the long retry algorithm.
335 long_retries: whether to use the long retry algorithm.
333336
334337 The regular retry algorithm makes 4 attempts, with intervals
335338 [0.5s, 1s, 2s].
345348 NB: the long retry algorithm takes over 20 minutes to complete, with
346349 a default timeout of 60s!
347350
348 ignore_backoff (bool): true to ignore the historical backoff data
351 ignore_backoff: true to ignore the historical backoff data
349352 and try the request anyway.
350353
351 backoff_on_404 (bool): Back off if we get a 404
354 backoff_on_404: Back off if we get a 404
352355
353356 Returns:
354 twisted.web.client.Response: resolves with the HTTP
355 response object on success.
357 Resolves with the HTTP response object on success.
356358
357359 Raises:
358360 HttpResponseException: If we get an HTTP response code >= 300
403405 )
404406
405407 # Inject the span into the headers
406 headers_dict = {}
408 headers_dict = {} # type: Dict[bytes, List[bytes]]
407409 inject_active_span_byte_dict(headers_dict, request.destination)
408410
409411 headers_dict[b"User-Agent"] = [self.version_string_bytes]
434436 data = encode_canonical_json(json)
435437 producer = QuieterFileBodyProducer(
436438 BytesIO(data), cooperator=self._cooperator
437 )
439 ) # type: Optional[IBodyProducer]
438440 else:
439441 producer = None
440442 auth_headers = self.build_auth_headers(
523525 )
524526 body = None
525527
526 e = HttpResponseException(response.code, response_phrase, body)
528 exc = HttpResponseException(
529 response.code, response_phrase, body
530 )
527531
528532 # Retry if the error is a 429 (Too Many Requests),
529533 # otherwise just raise a standard HttpResponseException
530534 if response.code == 429:
531 raise RequestSendFailed(e, can_retry=True) from e
535 raise RequestSendFailed(exc, can_retry=True) from exc
532536 else:
533 raise e
537 raise exc
534538
535539 break
536540 except RequestSendFailed as e:
581585 return response
582586
583587 def build_auth_headers(
584 self, destination, method, url_bytes, content=None, destination_is=None
585 ):
588 self,
589 destination: Optional[bytes],
590 method: bytes,
591 url_bytes: bytes,
592 content: Optional[JsonDict] = None,
593 destination_is: Optional[bytes] = None,
594 ) -> List[bytes]:
586595 """
587596 Builds the Authorization headers for a federation request
588597 Args:
589 destination (bytes|None): The destination homeserver of the request.
598 destination: The destination homeserver of the request.
590599 May be None if the destination is an identity server, in which case
591600 destination_is must be non-None.
592 method (bytes): The HTTP method of the request
593 url_bytes (bytes): The URI path of the request
594 content (object): The body of the request
595 destination_is (bytes): As 'destination', but if the destination is an
601 method: The HTTP method of the request
602 url_bytes: The URI path of the request
603 content: The body of the request
604 destination_is: As 'destination', but if the destination is an
596605 identity server
597606
598607 Returns:
599 list[bytes]: a list of headers to be added as "Authorization:" headers
608 A list of headers to be added as "Authorization:" headers
600609 """
601610 request = {
602611 "method": method.decode("ascii"),
628637
629638 async def put_json(
630639 self,
631 destination,
632 path,
633 args={},
634 data={},
635 json_data_callback=None,
636 long_retries=False,
637 timeout=None,
638 ignore_backoff=False,
639 backoff_on_404=False,
640 try_trailing_slash_on_400=False,
641 ):
640 destination: str,
641 path: str,
642 args: Optional[QueryArgs] = None,
643 data: Optional[JsonDict] = None,
644 json_data_callback: Optional[Callable[[], JsonDict]] = None,
645 long_retries: bool = False,
646 timeout: Optional[int] = None,
647 ignore_backoff: bool = False,
648 backoff_on_404: bool = False,
649 try_trailing_slash_on_400: bool = False,
650 ) -> Union[JsonDict, list]:
642651 """ Sends the specified json data using PUT
643652
644653 Args:
645 destination (str): The remote server to send the HTTP request
646 to.
647 path (str): The HTTP path.
648 args (dict): query params
649 data (dict): A dict containing the data that will be used as
654 destination: The remote server to send the HTTP request to.
655 path: The HTTP path.
656 args: query params
657 data: A dict containing the data that will be used as
650658 the request body. This will be encoded as JSON.
651 json_data_callback (callable): A callable returning the dict to
659 json_data_callback: A callable returning the dict to
652660 use as the request body.
653661
654 long_retries (bool): whether to use the long retry algorithm. See
662 long_retries: whether to use the long retry algorithm. See
655663 docs on _send_request for details.
656664
657 timeout (int|None): number of milliseconds to wait for the response.
665 timeout: number of milliseconds to wait for the response.
658666 self._default_timeout (60s) by default.
659667
660668 Note that we may make several attempts to send the request; this
662670 *each* attempt (including connection time) as well as the time spent
663671 reading the response body after a 200 response.
664672
665 ignore_backoff (bool): true to ignore the historical backoff data
673 ignore_backoff: true to ignore the historical backoff data
666674 and try the request anyway.
667 backoff_on_404 (bool): True if we should count a 404 response as
675 backoff_on_404: True if we should count a 404 response as
668676 a failure of the server (and should therefore back off future
669677 requests).
670 try_trailing_slash_on_400 (bool): True if on a 400 M_UNRECOGNIZED
678 try_trailing_slash_on_400: True if on a 400 M_UNRECOGNIZED
671679 response we should try appending a trailing slash to the end
672680 of the request. Workaround for #3622 in Synapse <= v0.99.3. This
673681 will be attempted before backing off if backing off has been
674682 enabled.
675683
676684 Returns:
677 dict|list: Succeeds when we get a 2xx HTTP response. The
685 Succeeds when we get a 2xx HTTP response. The
678686 result will be the decoded JSON body.
679687
680688 Raises:
720728
721729 async def post_json(
722730 self,
723 destination,
724 path,
725 data={},
726 long_retries=False,
727 timeout=None,
728 ignore_backoff=False,
729 args={},
730 ):
731 destination: str,
732 path: str,
733 data: Optional[JsonDict] = None,
734 long_retries: bool = False,
735 timeout: Optional[int] = None,
736 ignore_backoff: bool = False,
737 args: Optional[QueryArgs] = None,
738 ) -> Union[JsonDict, list]:
731739 """ Sends the specified json data using POST
732740
733741 Args:
734 destination (str): The remote server to send the HTTP request
735 to.
736
737 path (str): The HTTP path.
738
739 data (dict): A dict containing the data that will be used as
742 destination: The remote server to send the HTTP request to.
743
744 path: The HTTP path.
745
746 data: A dict containing the data that will be used as
740747 the request body. This will be encoded as JSON.
741748
742 long_retries (bool): whether to use the long retry algorithm. See
749 long_retries: whether to use the long retry algorithm. See
743750 docs on _send_request for details.
744751
745 timeout (int|None): number of milliseconds to wait for the response.
752 timeout: number of milliseconds to wait for the response.
746753 self._default_timeout (60s) by default.
747754
748755 Note that we may make several attempts to send the request; this
750757 *each* attempt (including connection time) as well as the time spent
751758 reading the response body after a 200 response.
752759
753 ignore_backoff (bool): true to ignore the historical backoff data and
760 ignore_backoff: true to ignore the historical backoff data and
754761 try the request anyway.
755762
756 args (dict): query params
763 args: query params
757764 Returns:
758765 dict|list: Succeeds when we get a 2xx HTTP response. The
759766 result will be the decoded JSON body.
794801
795802 async def get_json(
796803 self,
797 destination,
798 path,
799 args=None,
800 retry_on_dns_fail=True,
801 timeout=None,
802 ignore_backoff=False,
803 try_trailing_slash_on_400=False,
804 ):
804 destination: str,
805 path: str,
806 args: Optional[QueryArgs] = None,
807 retry_on_dns_fail: bool = True,
808 timeout: Optional[int] = None,
809 ignore_backoff: bool = False,
810 try_trailing_slash_on_400: bool = False,
811 ) -> Union[JsonDict, list]:
805812 """ GETs some json from the given host homeserver and path
806813
807814 Args:
808 destination (str): The remote server to send the HTTP request
809 to.
810
811 path (str): The HTTP path.
812
813 args (dict|None): A dictionary used to create query strings, defaults to
815 destination: The remote server to send the HTTP request to.
816
817 path: The HTTP path.
818
819 args: A dictionary used to create query strings, defaults to
814820 None.
815821
816 timeout (int|None): number of milliseconds to wait for the response.
822 timeout: number of milliseconds to wait for the response.
817823 self._default_timeout (60s) by default.
818824
819825 Note that we may make several attempts to send the request; this
821827 *each* attempt (including connection time) as well as the time spent
822828 reading the response body after a 200 response.
823829
824 ignore_backoff (bool): true to ignore the historical backoff data
830 ignore_backoff: true to ignore the historical backoff data
825831 and try the request anyway.
826832
827 try_trailing_slash_on_400 (bool): True if on a 400 M_UNRECOGNIZED
833 try_trailing_slash_on_400: True if on a 400 M_UNRECOGNIZED
828834 response we should try appending a trailing slash to the end of
829835 the request. Workaround for #3622 in Synapse <= v0.99.3.
830836 Returns:
831 dict|list: Succeeds when we get a 2xx HTTP response. The
837 Succeeds when we get a 2xx HTTP response. The
832838 result will be the decoded JSON body.
833839
834840 Raises:
869875
870876 async def delete_json(
871877 self,
872 destination,
873 path,
874 long_retries=False,
875 timeout=None,
876 ignore_backoff=False,
877 args={},
878 ):
878 destination: str,
879 path: str,
880 long_retries: bool = False,
881 timeout: Optional[int] = None,
882 ignore_backoff: bool = False,
883 args: Optional[QueryArgs] = None,
884 ) -> Union[JsonDict, list]:
879885 """Send a DELETE request to the remote expecting some json response
880886
881887 Args:
882 destination (str): The remote server to send the HTTP request
883 to.
884 path (str): The HTTP path.
885
886 long_retries (bool): whether to use the long retry algorithm. See
888 destination: The remote server to send the HTTP request to.
889 path: The HTTP path.
890
891 long_retries: whether to use the long retry algorithm. See
887892 docs on _send_request for details.
888893
889 timeout (int|None): number of milliseconds to wait for the response.
894 timeout: number of milliseconds to wait for the response.
890895 self._default_timeout (60s) by default.
891896
892897 Note that we may make several attempts to send the request; this
894899 *each* attempt (including connection time) as well as the time spent
895900 reading the response body after a 200 response.
896901
897 ignore_backoff (bool): true to ignore the historical backoff data and
902 ignore_backoff: true to ignore the historical backoff data and
898903 try the request anyway.
899904
900 args (dict): query params
905 args: query params
901906 Returns:
902 dict|list: Succeeds when we get a 2xx HTTP response. The
907 Succeeds when we get a 2xx HTTP response. The
903908 result will be the decoded JSON body.
904909
905910 Raises:
937942
938943 async def get_file(
939944 self,
940 destination,
941 path,
945 destination: str,
946 path: str,
942947 output_stream,
943 args={},
944 retry_on_dns_fail=True,
945 max_size=None,
946 ignore_backoff=False,
947 ):
948 args: Optional[QueryArgs] = None,
949 retry_on_dns_fail: bool = True,
950 max_size: Optional[int] = None,
951 ignore_backoff: bool = False,
952 ) -> Tuple[int, Dict[bytes, List[bytes]]]:
948953 """GETs a file from a given homeserver
949954 Args:
950 destination (str): The remote server to send the HTTP request to.
951 path (str): The HTTP path to GET.
952 output_stream (file): File to write the response body to.
953 args (dict): Optional dictionary used to create the query string.
954 ignore_backoff (bool): true to ignore the historical backoff data
955 destination: The remote server to send the HTTP request to.
956 path: The HTTP path to GET.
957 output_stream: File to write the response body to.
958 args: Optional dictionary used to create the query string.
959 ignore_backoff: true to ignore the historical backoff data
955960 and try the request anyway.
956961
957962 Returns:
958 tuple[int, dict]: Resolves with an (int,dict) tuple of
963 Resolves with an (int,dict) tuple of
959964 the file length and a dict of the response headers.
960965
961966 Raises:
979984 headers = dict(response.headers.getAllRawHeaders())
980985
981986 try:
982 d = _readBodyToFile(response, output_stream, max_size)
987 d = readBodyToFile(response, output_stream, max_size)
983988 d.addTimeout(self.default_timeout, self.reactor)
984989 length = await make_deferred_yieldable(d)
985990 except Exception as e:
10031008 return (length, headers)
10041009
10051010
1006 class _ReadBodyToFileProtocol(protocol.Protocol):
1007 def __init__(self, stream, deferred, max_size):
1008 self.stream = stream
1009 self.deferred = deferred
1010 self.length = 0
1011 self.max_size = max_size
1012
1013 def dataReceived(self, data):
1014 self.stream.write(data)
1015 self.length += len(data)
1016 if self.max_size is not None and self.length >= self.max_size:
1017 self.deferred.errback(
1018 SynapseError(
1019 502,
1020 "Requested file is too large > %r bytes" % (self.max_size,),
1021 Codes.TOO_LARGE,
1022 )
1023 )
1024 self.deferred = defer.Deferred()
1025 self.transport.loseConnection()
1026
1027 def connectionLost(self, reason):
1028 if reason.check(ResponseDone):
1029 self.deferred.callback(self.length)
1030 else:
1031 self.deferred.errback(reason)
1032
1033
1034 def _readBodyToFile(response, stream, max_size):
1035 d = defer.Deferred()
1036 response.deliverBody(_ReadBodyToFileProtocol(stream, d, max_size))
1037 return d
1038
1039
10401011 def _flatten_response_never_received(e):
10411012 if hasattr(e, "reasons"):
10421013 reasons = ", ".join(
10481019 return repr(e)
10491020
10501021
1051 def check_content_type_is_json(headers):
1022 def check_content_type_is_json(headers: Headers) -> None:
10521023 """
10531024 Check that a set of HTTP headers have a Content-Type header, and that it
10541025 is application/json.
10551026
10561027 Args:
1057 headers (twisted.web.http_headers.Headers): headers to check
1028 headers: headers to check
10581029
10591030 Raises:
10601031 RequestSendFailed: if the Content-Type header is missing or isn't JSON
10771048 ),
10781049 can_retry=False,
10791050 )
1080
1081
1082 def encode_query_args(args):
1083 if args is None:
1084 return b""
1085
1086 encoded_args = {}
1087 for k, vs in args.items():
1088 if isinstance(vs, str):
1089 vs = [vs]
1090 encoded_args[k] = [v.encode("UTF-8") for v in vs]
1091
1092 query_bytes = urllib.parse.urlencode(encoded_args, True)
1093
1094 return query_bytes.encode("utf8")
2424 from typing import Any, Callable, Dict, Iterator, List, Tuple, Union
2525
2626 import jinja2
27 from canonicaljson import iterencode_canonical_json, iterencode_pretty_printed_json
27 from canonicaljson import iterencode_canonical_json
2828 from zope.interface import implementer
2929
3030 from twisted.internet import defer, interfaces
9393 pass
9494 else:
9595 respond_with_json(
96 request,
97 error_code,
98 error_dict,
99 send_cors=True,
100 pretty_print=_request_user_agent_is_curl(request),
96 request, error_code, error_dict, send_cors=True,
10197 )
10298
10399
289285 code,
290286 response_object,
291287 send_cors=True,
292 pretty_print=_request_user_agent_is_curl(request),
293288 canonical_json=self.canonical_json,
294289 )
295290
586581 code: int,
587582 json_object: Any,
588583 send_cors: bool = False,
589 pretty_print: bool = False,
590584 canonical_json: bool = True,
591585 ):
592586 """Sends encoded JSON in response to the given request.
597591 json_object: The object to serialize to JSON.
598592 send_cors: Whether to send Cross-Origin Resource Sharing headers
599593 https://fetch.spec.whatwg.org/#http-cors-protocol
600 pretty_print: Whether to include indentation and line-breaks in the
601 resulting JSON bytes.
602594 canonical_json: Whether to use the canonicaljson algorithm when encoding
603595 the JSON bytes.
604596
614606 )
615607 return None
616608
617 if pretty_print:
618 encoder = iterencode_pretty_printed_json
609 if canonical_json:
610 encoder = iterencode_canonical_json
619611 else:
620 if canonical_json:
621 encoder = iterencode_canonical_json
622 else:
623 encoder = _encode_json_bytes
612 encoder = _encode_json_bytes
624613
625614 request.setResponseCode(code)
626615 request.setHeader(b"Content-Type", b"application/json")
684673 )
685674 request.setHeader(
686675 b"Access-Control-Allow-Headers",
687 b"Origin, X-Requested-With, Content-Type, Accept, Authorization",
676 b"Origin, X-Requested-With, Content-Type, Accept, Authorization, Date",
688677 )
689678
690679
758747 request.finish()
759748 except RuntimeError as e:
760749 logger.info("Connection disconnected before response was written: %r", e)
761
762
763 def _request_user_agent_is_curl(request: Request) -> bool:
764 user_agents = request.requestHeaders.getRawHeaders(b"User-Agent", default=[])
765 for user_agent in user_agents:
766 if b"curl" in user_agent:
767 return True
768 return False
4848 self._store = hs.get_datastore()
4949 self._auth = hs.get_auth()
5050 self._auth_handler = auth_handler
51 self._server_name = hs.hostname
5152
5253 # We expose these as properties below in order to attach a helpful docstring.
5354 self._http_client = hs.get_simple_http_client() # type: SimpleHttpClient
335336 SynapseError if the event was not allowed.
336337 """
337338 # Create a requester object
338 requester = create_requester(event_dict["sender"])
339 requester = create_requester(
340 event_dict["sender"], authenticated_entity=self._server_name
341 )
339342
340343 # Create and send the event
341344 (
7474 self.failing_since = pusherdict["failing_since"]
7575 self.timed_call = None
7676 self._is_processing = False
77 self._group_unread_count_by_room = hs.config.push_group_unread_count_by_room
7778
7879 # This is the highest stream ordering we know it's safe to process.
7980 # When new events arrive, we'll be given a window of new events: we
135136 async def _update_badge(self):
136137 # XXX as per https://github.com/matrix-org/matrix-doc/issues/2627, this seems
137138 # to be largely redundant. perhaps we can remove it.
138 badge = await push_tools.get_badge_count(self.hs.get_datastore(), self.user_id)
139 badge = await push_tools.get_badge_count(
140 self.hs.get_datastore(),
141 self.user_id,
142 group_by_room=self._group_unread_count_by_room,
143 )
139144 await self._send_badge(badge)
140145
141146 def on_timer(self):
282287 return True
283288
284289 tweaks = push_rule_evaluator.tweaks_for_actions(push_action["actions"])
285 badge = await push_tools.get_badge_count(self.hs.get_datastore(), self.user_id)
290 badge = await push_tools.get_badge_count(
291 self.hs.get_datastore(),
292 self.user_id,
293 group_by_room=self._group_unread_count_by_room,
294 )
286295
287296 event = await self.store.get_event(push_action["event_id"], allow_none=True)
288297 if event is None:
1111 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212 # See the License for the specific language governing permissions and
1313 # limitations under the License.
14
1514 from synapse.push.presentable_names import calculate_room_name, name_from_member_event
1615 from synapse.storage import Storage
16 from synapse.storage.databases.main import DataStore
1717
1818
19 async def get_badge_count(store, user_id):
19 async def get_badge_count(store: DataStore, user_id: str, group_by_room: bool) -> int:
2020 invites = await store.get_invited_rooms_for_local_user(user_id)
2121 joins = await store.get_rooms_for_user(user_id)
2222
3333 room_id, user_id, last_unread_event_id
3434 )
3535 )
36 # return one badge count per conversation, as count per
37 # message is so noisy as to be almost useless
38 badge += 1 if notifs["notify_count"] else 0
36 if notifs["notify_count"] == 0:
37 continue
38
39 if group_by_room:
40 # return one badge count per conversation
41 badge += 1
42 else:
43 # increment the badge count by the number of unread messages in the room
44 badge += notifs["notify_count"]
3945 return badge
4046
4147
3838 #
3939 # Note that these both represent runtime dependencies (and the versions
4040 # installed are checked at runtime).
41 #
42 # Also note that we replicate these constraints in the Synapse Dockerfile while
43 # pre-installing dependencies. If these constraints are updated here, the same
44 # change should be made in the Dockerfile.
4145 #
4246 # [1] https://pip.pypa.io/en/stable/reference/pip_install/#requirement-specifiers.
4347
6872 "msgpack>=0.5.2",
6973 "phonenumbers>=8.2.0",
7074 # we use GaugeHistogramMetric, which was added in prom-client 0.4.0.
71 # prom-client has a history of breaking backwards compatibility between
72 # minor versions (https://github.com/prometheus/client_python/issues/317),
73 # so we also pin the minor version.
74 #
75 # Note that we replicate these constraints in the Synapse Dockerfile while
76 # pre-installing dependencies. If these constraints are updated here, the
77 # same change should be made in the Dockerfile.
78 "prometheus_client>=0.4.0,<0.9.0",
75 "prometheus_client>=0.4.0",
7976 # we use attr.validators.deep_iterable, which arrived in 19.1.0 (Note:
8077 # Fedora 31 only has 19.1, so if we want to upgrade we should wait until 33
8178 # is out in November.)
9895 # python 3.5.2, as per https://github.com/itamarst/eliot/issues/418
9996 'eliot<1.8.0;python_version<"3.5.3"',
10097 ],
101 "saml2": ["pysaml2>=4.5.0"],
98 "saml2": [
99 # pysaml2 6.4.0 is incompatible with Python 3.5 (see https://github.com/IdentityPython/pysaml2/issues/749)
100 "pysaml2>=4.5.0,<6.4.0;python_version<'3.6'",
101 "pysaml2>=4.5.0;python_version>='3.6'",
102 ],
102103 "oidc": ["authlib>=0.14.0"],
103104 "systemd": ["systemd-python>=231"],
104105 "url_preview": ["lxml>=3.5.0"],
253253 return 200, {}
254254
255255
256 class ReplicationStoreRoomOnInviteRestServlet(ReplicationEndpoint):
256 class ReplicationStoreRoomOnOutlierMembershipRestServlet(ReplicationEndpoint):
257257 """Called to clean up any data in DB for a given room, ready for the
258258 server to join the room.
259259
260260 Request format:
261261
262 POST /_synapse/replication/store_room_on_invite/:room_id/:txn_id
262 POST /_synapse/replication/store_room_on_outlier_membership/:room_id/:txn_id
263263
264264 {
265265 "room_version": "1",
266266 }
267267 """
268268
269 NAME = "store_room_on_invite"
269 NAME = "store_room_on_outlier_membership"
270270 PATH_ARGS = ("room_id",)
271271
272272 def __init__(self, hs):
281281 async def _handle_request(self, request, room_id):
282282 content = parse_json_object_from_request(request)
283283 room_version = KNOWN_ROOM_VERSIONS[content["room_version"]]
284 await self.store.maybe_store_room_on_invite(room_id, room_version)
284 await self.store.maybe_store_room_on_outlier_membership(room_id, room_version)
285285 return 200, {}
286286
287287
290290 ReplicationFederationSendEduRestServlet(hs).register(http_server)
291291 ReplicationGetQueryRestServlet(hs).register(http_server)
292292 ReplicationCleanRoomRestServlet(hs).register(http_server)
293 ReplicationStoreRoomOnInviteRestServlet(hs).register(http_server)
293 ReplicationStoreRoomOnOutlierMembershipRestServlet(hs).register(http_server)
1111 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212 # See the License for the specific language governing permissions and
1313 # limitations under the License.
14
1514 import logging
16 from typing import TYPE_CHECKING, Optional
15 from typing import TYPE_CHECKING, List, Optional, Tuple
16
17 from twisted.web.http import Request
1718
1819 from synapse.http.servlet import parse_json_object_from_request
1920 from synapse.replication.http._base import ReplicationEndpoint
5152 self.clock = hs.get_clock()
5253
5354 @staticmethod
54 async def _serialize_payload(
55 requester, room_id, user_id, remote_room_hosts, content
56 ):
55 async def _serialize_payload( # type: ignore
56 requester: Requester,
57 room_id: str,
58 user_id: str,
59 remote_room_hosts: List[str],
60 content: JsonDict,
61 ) -> JsonDict:
5762 """
5863 Args:
59 requester(Requester)
60 room_id (str)
61 user_id (str)
62 remote_room_hosts (list[str]): Servers to try and join via
63 content(dict): The event content to use for the join event
64 requester: The user making the request according to the access token
65 room_id: The ID of the room.
66 user_id: The ID of the user.
67 remote_room_hosts: Servers to try and join via
68 content: The event content to use for the join event
69
70 Returns:
71 A dict representing the payload of the request.
6472 """
6573 return {
6674 "requester": requester.serialize(),
6876 "content": content,
6977 }
7078
71 async def _handle_request(self, request, room_id, user_id):
79 async def _handle_request( # type: ignore
80 self, request: Request, room_id: str, user_id: str
81 ) -> Tuple[int, JsonDict]:
7282 content = parse_json_object_from_request(request)
7383
7484 remote_room_hosts = content["remote_room_hosts"]
117127 txn_id: Optional[str],
118128 requester: Requester,
119129 content: JsonDict,
120 ):
130 ) -> JsonDict:
121131 """
122132 Args:
123 invite_event_id: ID of the invite to be rejected
124 txn_id: optional transaction ID supplied by the client
125 requester: user making the rejection request, according to the access token
126 content: additional content to include in the rejection event.
133 invite_event_id: The ID of the invite to be rejected.
134 txn_id: Optional transaction ID supplied by the client
135 requester: User making the rejection request, according to the access token
136 content: Additional content to include in the rejection event.
127137 Normally an empty dict.
138
139 Returns:
140 A dict representing the payload of the request.
128141 """
129142 return {
130143 "txn_id": txn_id,
132145 "content": content,
133146 }
134147
135 async def _handle_request(self, request, invite_event_id):
148 async def _handle_request( # type: ignore
149 self, request: Request, invite_event_id: str
150 ) -> Tuple[int, JsonDict]:
136151 content = parse_json_object_from_request(request)
137152
138153 txn_id = content["txn_id"]
173188 self.distributor = hs.get_distributor()
174189
175190 @staticmethod
176 async def _serialize_payload(room_id, user_id, change):
191 async def _serialize_payload( # type: ignore
192 room_id: str, user_id: str, change: str
193 ) -> JsonDict:
177194 """
178195 Args:
179 room_id (str)
180 user_id (str)
181 change (str): "left"
196 room_id: The ID of the room.
197 user_id: The ID of the user.
198 change: "left"
199
200 Returns:
201 A dict representing the payload of the request.
182202 """
183203 assert change == "left"
184204
185205 return {}
186206
187 def _handle_request(self, request, room_id, user_id, change):
207 def _handle_request( # type: ignore
208 self, request: Request, room_id: str, user_id: str, change: str
209 ) -> Tuple[int, JsonDict]:
188210 logger.info("user membership change: %s in %s", user_id, room_id)
189211
190212 user = UserID.from_string(user_id)
2020 from synapse.api.errors import Codes, NotFoundError, SynapseError
2121 from synapse.http.server import JsonResource
2222 from synapse.http.servlet import RestServlet, parse_json_object_from_request
23 from synapse.rest.admin._base import (
24 admin_patterns,
25 assert_requester_is_admin,
26 historical_admin_path_patterns,
27 )
23 from synapse.rest.admin._base import admin_patterns, assert_requester_is_admin
2824 from synapse.rest.admin.devices import (
2925 DeleteDevicesRestServlet,
3026 DeviceRestServlet,
6056 UserRestServletV2,
6157 UsersRestServlet,
6258 UsersRestServletV2,
59 UserTokenRestServlet,
6360 WhoisRestServlet,
6461 )
6562 from synapse.types import RoomStreamToken
8279
8380
8481 class PurgeHistoryRestServlet(RestServlet):
85 PATTERNS = historical_admin_path_patterns(
82 PATTERNS = admin_patterns(
8683 "/purge_history/(?P<room_id>[^/]*)(/(?P<event_id>[^/]+))?"
8784 )
8885
167164
168165
169166 class PurgeHistoryStatusRestServlet(RestServlet):
170 PATTERNS = historical_admin_path_patterns(
171 "/purge_history_status/(?P<purge_id>[^/]+)"
172 )
167 PATTERNS = admin_patterns("/purge_history_status/(?P<purge_id>[^/]+)")
173168
174169 def __init__(self, hs):
175170 """
222217 UserAdminServlet(hs).register(http_server)
223218 UserMediaRestServlet(hs).register(http_server)
224219 UserMembershipRestServlet(hs).register(http_server)
220 UserTokenRestServlet(hs).register(http_server)
225221 UserRestServletV2(hs).register(http_server)
226222 UsersRestServletV2(hs).register(http_server)
227223 DeviceRestServlet(hs).register(http_server)
1919 import synapse.api.auth
2020 from synapse.api.errors import AuthError
2121 from synapse.types import UserID
22
23
24 def historical_admin_path_patterns(path_regex):
25 """Returns the list of patterns for an admin endpoint, including historical ones
26
27 This is a backwards-compatibility hack. Previously, the Admin API was exposed at
28 various paths under /_matrix/client. This function returns a list of patterns
29 matching those paths (as well as the new one), so that existing scripts which rely
30 on the endpoints being available there are not broken.
31
32 Note that this should only be used for existing endpoints: new ones should just
33 register for the /_synapse/admin path.
34 """
35 return [
36 re.compile(prefix + path_regex)
37 for prefix in (
38 "^/_synapse/admin/v1",
39 "^/_matrix/client/api/v1/admin",
40 "^/_matrix/client/unstable/admin",
41 "^/_matrix/client/r0/admin",
42 )
43 ]
4422
4523
4624 def admin_patterns(path_regex: str, version: str = "v1"):
1515
1616 from synapse.api.errors import SynapseError
1717 from synapse.http.servlet import RestServlet
18 from synapse.rest.admin._base import (
19 assert_user_is_admin,
20 historical_admin_path_patterns,
21 )
18 from synapse.rest.admin._base import admin_patterns, assert_user_is_admin
2219
2320 logger = logging.getLogger(__name__)
2421
2724 """Allows deleting of local groups
2825 """
2926
30 PATTERNS = historical_admin_path_patterns("/delete_group/(?P<group_id>[^/]*)")
27 PATTERNS = admin_patterns("/delete_group/(?P<group_id>[^/]*)")
3128
3229 def __init__(self, hs):
3330 self.group_server = hs.get_groups_server_handler()
2121 admin_patterns,
2222 assert_requester_is_admin,
2323 assert_user_is_admin,
24 historical_admin_path_patterns,
2524 )
2625
2726 logger = logging.getLogger(__name__)
3332 """
3433
3534 PATTERNS = (
36 historical_admin_path_patterns("/room/(?P<room_id>[^/]+)/media/quarantine")
35 admin_patterns("/room/(?P<room_id>[^/]+)/media/quarantine")
3736 +
3837 # This path kept around for legacy reasons
39 historical_admin_path_patterns("/quarantine_media/(?P<room_id>[^/]+)")
38 admin_patterns("/quarantine_media/(?P<room_id>[^/]+)")
4039 )
4140
4241 def __init__(self, hs):
6261 this server.
6362 """
6463
65 PATTERNS = historical_admin_path_patterns(
66 "/user/(?P<user_id>[^/]+)/media/quarantine"
67 )
64 PATTERNS = admin_patterns("/user/(?P<user_id>[^/]+)/media/quarantine")
6865
6966 def __init__(self, hs):
7067 self.store = hs.get_datastore()
8986 it via this server.
9087 """
9188
92 PATTERNS = historical_admin_path_patterns(
89 PATTERNS = admin_patterns(
9390 "/media/quarantine/(?P<server_name>[^/]+)/(?P<media_id>[^/]+)"
9491 )
9592
115112 """Lists all of the media in a given room.
116113 """
117114
118 PATTERNS = historical_admin_path_patterns("/room/(?P<room_id>[^/]+)/media")
115 PATTERNS = admin_patterns("/room/(?P<room_id>[^/]+)/media")
119116
120117 def __init__(self, hs):
121118 self.store = hs.get_datastore()
133130
134131
135132 class PurgeMediaCacheRestServlet(RestServlet):
136 PATTERNS = historical_admin_path_patterns("/purge_media_cache")
133 PATTERNS = admin_patterns("/purge_media_cache")
137134
138135 def __init__(self, hs):
139136 self.media_repository = hs.get_media_repository()
2828 admin_patterns,
2929 assert_requester_is_admin,
3030 assert_user_is_admin,
31 historical_admin_path_patterns,
3231 )
3332 from synapse.storage.databases.main.room import RoomSortOrder
3433 from synapse.types import RoomAlias, RoomID, UserID, create_requester
4342 joined to the new room.
4443 """
4544
46 PATTERNS = historical_admin_path_patterns("/shutdown_room/(?P<room_id>[^/]+)")
45 PATTERNS = admin_patterns("/shutdown_room/(?P<room_id>[^/]+)")
4746
4847 def __init__(self, hs):
4948 self.hs = hs
7069
7170
7271 class DeleteRoomRestServlet(RestServlet):
73 """Delete a room from server. It is a combination and improvement of
74 shut down and purge room.
72 """Delete a room from server.
73
74 It is a combination and improvement of shutdown and purge room.
75
7576 Shuts down a room by removing all local users from the room.
7677 Blocking all future invites and joins to the room is optional.
78
7779 If desired any local aliases will be repointed to a new room
78 created by `new_room_user_id` and kicked users will be auto
80 created by `new_room_user_id` and kicked users will be auto-
7981 joined to the new room.
80 It will remove all trace of a room from the database.
82
83 If 'purge' is true, it will remove all traces of a room from the database.
8184 """
8285
8386 PATTERNS = admin_patterns("/rooms/(?P<room_id>[^/]+)/delete$")
107110 raise SynapseError(
108111 HTTPStatus.BAD_REQUEST,
109112 "Param 'purge' must be a boolean, if given",
113 Codes.BAD_JSON,
114 )
115
116 force_purge = content.get("force_purge", False)
117 if not isinstance(force_purge, bool):
118 raise SynapseError(
119 HTTPStatus.BAD_REQUEST,
120 "Param 'force_purge' must be a boolean, if given",
110121 Codes.BAD_JSON,
111122 )
112123
121132
122133 # Purge room
123134 if purge:
124 await self.pagination_handler.purge_room(room_id)
135 await self.pagination_handler.purge_room(room_id, force=force_purge)
125136
126137 return (200, ret)
127138
308319 400, "%s was not legal room ID or room alias" % (room_identifier,)
309320 )
310321
311 fake_requester = create_requester(target_user)
322 fake_requester = create_requester(
323 target_user, authenticated_entity=requester.authenticated_entity
324 )
312325
313326 # send invite if room has "JoinRules.INVITE"
314327 room_state = await self.state_handler.get_current_state(room_id)
1515 import hmac
1616 import logging
1717 from http import HTTPStatus
18 from typing import Tuple
18 from typing import TYPE_CHECKING, Tuple
1919
2020 from synapse.api.constants import UserTypes
2121 from synapse.api.errors import Codes, NotFoundError, SynapseError
3232 admin_patterns,
3333 assert_requester_is_admin,
3434 assert_user_is_admin,
35 historical_admin_path_patterns,
3635 )
36 from synapse.rest.client.v2_alpha._base import client_patterns
3737 from synapse.types import JsonDict, UserID
38
39 if TYPE_CHECKING:
40 from synapse.server import HomeServer
3841
3942 logger = logging.getLogger(__name__)
4043
5154
5255
5356 class UsersRestServlet(RestServlet):
54 PATTERNS = historical_admin_path_patterns("/users/(?P<user_id>[^/]*)$")
57 PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)$")
5558
5659 def __init__(self, hs):
5760 self.hs = hs
334337 nonce to the time it was generated, in int seconds.
335338 """
336339
337 PATTERNS = historical_admin_path_patterns("/register")
340 PATTERNS = admin_patterns("/register")
338341 NONCE_TIMEOUT = 60
339342
340343 def __init__(self, hs):
457460
458461
459462 class WhoisRestServlet(RestServlet):
460 PATTERNS = historical_admin_path_patterns("/whois/(?P<user_id>[^/]*)")
463 path_regex = "/whois/(?P<user_id>[^/]*)$"
464 PATTERNS = (
465 admin_patterns(path_regex)
466 +
467 # URL for spec reason
468 # https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-admin-whois-userid
469 client_patterns("/admin" + path_regex, v1=True)
470 )
461471
462472 def __init__(self, hs):
463473 self.hs = hs
481491
482492
483493 class DeactivateAccountRestServlet(RestServlet):
484 PATTERNS = historical_admin_path_patterns("/deactivate/(?P<target_user_id>[^/]*)")
494 PATTERNS = admin_patterns("/deactivate/(?P<target_user_id>[^/]*)")
485495
486496 def __init__(self, hs):
487497 self._deactivate_account_handler = hs.get_deactivate_account_handler()
512522
513523
514524 class AccountValidityRenewServlet(RestServlet):
515 PATTERNS = historical_admin_path_patterns("/account_validity/validity$")
525 PATTERNS = admin_patterns("/account_validity/validity$")
516526
517527 def __init__(self, hs):
518528 """
555565 200 OK with empty object if success otherwise an error.
556566 """
557567
558 PATTERNS = historical_admin_path_patterns(
559 "/reset_password/(?P<target_user_id>[^/]*)"
560 )
568 PATTERNS = admin_patterns("/reset_password/(?P<target_user_id>[^/]*)")
561569
562570 def __init__(self, hs):
563571 self.store = hs.get_datastore()
599607 200 OK with json object {list[dict[str, Any]], count} or empty object.
600608 """
601609
602 PATTERNS = historical_admin_path_patterns("/search_users/(?P<target_user_id>[^/]*)")
610 PATTERNS = admin_patterns("/search_users/(?P<target_user_id>[^/]*)")
603611
604612 def __init__(self, hs):
605613 self.hs = hs
827835 ret["next_token"] = start + len(media)
828836
829837 return 200, ret
838
839
840 class UserTokenRestServlet(RestServlet):
841 """An admin API for logging in as a user.
842
843 Example:
844
845 POST /_synapse/admin/v1/users/@test:example.com/login
846 {}
847
848 200 OK
849 {
850 "access_token": "<some_token>"
851 }
852 """
853
854 PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)/login$")
855
856 def __init__(self, hs: "HomeServer"):
857 self.hs = hs
858 self.store = hs.get_datastore()
859 self.auth = hs.get_auth()
860 self.auth_handler = hs.get_auth_handler()
861
862 async def on_POST(self, request, user_id):
863 requester = await self.auth.get_user_by_req(request)
864 await assert_user_is_admin(self.auth, requester.user)
865 auth_user = requester.user
866
867 if not self.hs.is_mine_id(user_id):
868 raise SynapseError(400, "Only local users can be logged in as")
869
870 body = parse_json_object_from_request(request, allow_empty_body=True)
871
872 valid_until_ms = body.get("valid_until_ms")
873 if valid_until_ms and not isinstance(valid_until_ms, int):
874 raise SynapseError(400, "'valid_until_ms' parameter must be an int")
875
876 if auth_user.to_string() == user_id:
877 raise SynapseError(400, "Cannot use admin API to login as self")
878
879 token = await self.auth_handler.get_access_token_for_user_id(
880 user_id=auth_user.to_string(),
881 device_id=None,
882 valid_until_ms=valid_until_ms,
883 puppets_user_id=user_id,
884 )
885
886 return 200, {"access_token": token}
1818 from synapse.api.errors import Codes, LoginError, SynapseError
1919 from synapse.api.ratelimiting import Ratelimiter
2020 from synapse.appservice import ApplicationService
21 from synapse.handlers.auth import (
22 convert_client_dict_legacy_fields_to_identifier,
23 login_id_phone_to_thirdparty,
24 )
2521 from synapse.http.server import finish_request
2622 from synapse.http.servlet import (
2723 RestServlet,
3228 from synapse.rest.client.v2_alpha._base import client_patterns
3329 from synapse.rest.well_known import WellKnownBuilder
3430 from synapse.types import JsonDict, UserID
35 from synapse.util.threepids import canonicalise_email
3631
3732 logger = logging.getLogger(__name__)
3833
7671 clock=hs.get_clock(),
7772 rate_hz=self.hs.config.rc_login_account.per_second,
7873 burst_count=self.hs.config.rc_login_account.burst_count,
79 )
80 self._failed_attempts_ratelimiter = Ratelimiter(
81 clock=hs.get_clock(),
82 rate_hz=self.hs.config.rc_login_failed_attempts.per_second,
83 burst_count=self.hs.config.rc_login_failed_attempts.burst_count,
8474 )
8575
8676 def on_GET(self, request: SynapseRequest):
139129 result["well_known"] = well_known_data
140130 return 200, result
141131
142 def _get_qualified_user_id(self, identifier):
143 if identifier["type"] != "m.id.user":
144 raise SynapseError(400, "Unknown login identifier type")
145 if "user" not in identifier:
146 raise SynapseError(400, "User identifier is missing 'user' key")
147
148 if identifier["user"].startswith("@"):
149 return identifier["user"]
150 else:
151 return UserID(identifier["user"], self.hs.hostname).to_string()
152
153132 async def _do_appservice_login(
154133 self, login_submission: JsonDict, appservice: ApplicationService
155134 ):
156 logger.info(
157 "Got appservice login request with identifier: %r",
158 login_submission.get("identifier"),
159 )
160
161 identifier = convert_client_dict_legacy_fields_to_identifier(login_submission)
162 qualified_user_id = self._get_qualified_user_id(identifier)
135 identifier = login_submission.get("identifier")
136 logger.info("Got appservice login request with identifier: %r", identifier)
137
138 if not isinstance(identifier, dict):
139 raise SynapseError(
140 400, "Invalid identifier in login submission", Codes.INVALID_PARAM
141 )
142
143 # this login flow only supports identifiers of type "m.id.user".
144 if identifier.get("type") != "m.id.user":
145 raise SynapseError(
146 400, "Unknown login identifier type", Codes.INVALID_PARAM
147 )
148
149 user = identifier.get("user")
150 if not isinstance(user, str):
151 raise SynapseError(400, "Invalid user in identifier", Codes.INVALID_PARAM)
152
153 if user.startswith("@"):
154 qualified_user_id = user
155 else:
156 qualified_user_id = UserID(user, self.hs.hostname).to_string()
163157
164158 if not appservice.is_interested_in_user(qualified_user_id):
165159 raise LoginError(403, "Invalid access_token", errcode=Codes.FORBIDDEN)
185179 login_submission.get("address"),
186180 login_submission.get("user"),
187181 )
188 identifier = convert_client_dict_legacy_fields_to_identifier(login_submission)
189
190 # convert phone type identifiers to generic threepids
191 if identifier["type"] == "m.id.phone":
192 identifier = login_id_phone_to_thirdparty(identifier)
193
194 # convert threepid identifiers to user IDs
195 if identifier["type"] == "m.id.thirdparty":
196 address = identifier.get("address")
197 medium = identifier.get("medium")
198
199 if medium is None or address is None:
200 raise SynapseError(400, "Invalid thirdparty identifier")
201
202 # For emails, canonicalise the address.
203 # We store all email addresses canonicalised in the DB.
204 # (See add_threepid in synapse/handlers/auth.py)
205 if medium == "email":
206 try:
207 address = canonicalise_email(address)
208 except ValueError as e:
209 raise SynapseError(400, str(e))
210
211 # We also apply account rate limiting using the 3PID as a key, as
212 # otherwise using 3PID bypasses the ratelimiting based on user ID.
213 self._failed_attempts_ratelimiter.ratelimit((medium, address), update=False)
214
215 # Check for login providers that support 3pid login types
216 (
217 canonical_user_id,
218 callback_3pid,
219 ) = await self.auth_handler.check_password_provider_3pid(
220 medium, address, login_submission["password"]
221 )
222 if canonical_user_id:
223 # Authentication through password provider and 3pid succeeded
224
225 result = await self._complete_login(
226 canonical_user_id, login_submission, callback_3pid
227 )
228 return result
229
230 # No password providers were able to handle this 3pid
231 # Check local store
232 user_id = await self.hs.get_datastore().get_user_id_by_threepid(
233 medium, address
234 )
235 if not user_id:
236 logger.warning(
237 "unknown 3pid identifier medium %s, address %r", medium, address
238 )
239 # We mark that we've failed to log in here, as
240 # `check_password_provider_3pid` might have returned `None` due
241 # to an incorrect password, rather than the account not
242 # existing.
243 #
244 # If it returned None but the 3PID was bound then we won't hit
245 # this code path, which is fine as then the per-user ratelimit
246 # will kick in below.
247 self._failed_attempts_ratelimiter.can_do_action((medium, address))
248 raise LoginError(403, "", errcode=Codes.FORBIDDEN)
249
250 identifier = {"type": "m.id.user", "user": user_id}
251
252 # by this point, the identifier should be an m.id.user: if it's anything
253 # else, we haven't understood it.
254 qualified_user_id = self._get_qualified_user_id(identifier)
255
256 # Check if we've hit the failed ratelimit (but don't update it)
257 self._failed_attempts_ratelimiter.ratelimit(
258 qualified_user_id.lower(), update=False
259 )
260
261 try:
262 canonical_user_id, callback = await self.auth_handler.validate_login(
263 identifier["user"], login_submission
264 )
265 except LoginError:
266 # The user has failed to log in, so we need to update the rate
267 # limiter. Using `can_do_action` avoids us raising a ratelimit
268 # exception and masking the LoginError. The actual ratelimiting
269 # should have happened above.
270 self._failed_attempts_ratelimiter.can_do_action(qualified_user_id.lower())
271 raise
272
182 canonical_user_id, callback = await self.auth_handler.validate_login(
183 login_submission, ratelimit=True
184 )
273185 result = await self._complete_login(
274186 canonical_user_id, login_submission, callback
275187 )
1717
1818 import logging
1919 import re
20 from typing import List, Optional
20 from typing import TYPE_CHECKING, List, Optional
2121 from urllib import parse as urlparse
2222
2323 from synapse.api.constants import EventTypes, Membership
4747 from synapse.util import json_decoder
4848 from synapse.util.stringutils import random_string
4949
50 MYPY = False
51 if MYPY:
50 if TYPE_CHECKING:
5251 import synapse.server
5352
5453 logger = logging.getLogger(__name__)
114114 # comments for request_token_inhibit_3pid_errors.
115115 # Also wait for some random amount of time between 100ms and 1s to make it
116116 # look like we did something.
117 await self.hs.clock.sleep(random.randint(1, 10) / 10)
117 await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
118118 return 200, {"sid": random_string(16)}
119119
120120 raise SynapseError(400, "Email not found", Codes.THREEPID_NOT_FOUND)
386386 # comments for request_token_inhibit_3pid_errors.
387387 # Also wait for some random amount of time between 100ms and 1s to make it
388388 # look like we did something.
389 await self.hs.clock.sleep(random.randint(1, 10) / 10)
389 await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
390390 return 200, {"sid": random_string(16)}
391391
392392 raise SynapseError(400, "Email is already in use", Codes.THREEPID_IN_USE)
465465 # comments for request_token_inhibit_3pid_errors.
466466 # Also wait for some random amount of time between 100ms and 1s to make it
467467 # look like we did something.
468 await self.hs.clock.sleep(random.randint(1, 10) / 10)
468 await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
469469 return 200, {"sid": random_string(16)}
470470
471471 raise SynapseError(400, "MSISDN is already in use", Codes.THREEPID_IN_USE)
134134 # comments for request_token_inhibit_3pid_errors.
135135 # Also wait for some random amount of time between 100ms and 1s to make it
136136 # look like we did something.
137 await self.hs.clock.sleep(random.randint(1, 10) / 10)
137 await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
138138 return 200, {"sid": random_string(16)}
139139
140140 raise SynapseError(400, "Email is already in use", Codes.THREEPID_IN_USE)
213213 # comments for request_token_inhibit_3pid_errors.
214214 # Also wait for some random amount of time between 100ms and 1s to make it
215215 # look like we did something.
216 await self.hs.clock.sleep(random.randint(1, 10) / 10)
216 await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
217217 return 200, {"sid": random_string(16)}
218218
219219 raise SynapseError(
170170 )
171171 with context:
172172 sync_result = await self.sync_handler.wait_for_sync_for_user(
173 requester,
173174 sync_config,
174175 since_token=since_token,
175176 timeout=timeout,
6565
6666 def __init__(self, hs):
6767 self.config = hs.config
68 self.clock = hs.clock
68 self.clock = hs.get_clock()
6969 self.update_response_body(self.clock.time_msec())
7070 Resource.__init__(self)
7171
2626 import os
2727 from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, TypeVar, cast
2828
29 import twisted
29 import twisted.internet.base
30 import twisted.internet.tcp
3031 from twisted.mail.smtp import sendmail
3132 from twisted.web.iweb import IPolicyForHTTPS
3233
8889 from synapse.handlers.room_member_worker import RoomMemberWorkerHandler
8990 from synapse.handlers.search import SearchHandler
9091 from synapse.handlers.set_password import SetPasswordHandler
92 from synapse.handlers.sso import SsoHandler
9193 from synapse.handlers.stats import StatsHandler
9294 from synapse.handlers.sync import SyncHandler
9395 from synapse.handlers.typing import FollowerTypingHandler, TypingWriterHandler
144146 "@cache_in_self can only be used on functions starting with `get_`"
145147 )
146148
147 depname = builder.__name__[len("get_") :]
149 # get_attr -> _attr
150 depname = builder.__name__[len("get") :]
148151
149152 building = [False]
150153
232235 self._instance_id = random_string(5)
233236 self._instance_name = config.worker_name or "master"
234237
235 self.clock = Clock(reactor)
236 self.distributor = Distributor()
237
238 self.registration_ratelimiter = Ratelimiter(
239 clock=self.clock,
240 rate_hz=config.rc_registration.per_second,
241 burst_count=config.rc_registration.burst_count,
242 )
243
244238 self.version_string = version_string
245239
246240 self.datastores = None # type: Optional[Databases]
298292 def is_mine_id(self, string: str) -> bool:
299293 return string.split(":", 1)[1] == self.hostname
300294
295 @cache_in_self
301296 def get_clock(self) -> Clock:
302 return self.clock
297 return Clock(self._reactor)
303298
304299 def get_datastore(self) -> DataStore:
305300 if not self.datastores:
316311 def get_config(self) -> HomeServerConfig:
317312 return self.config
318313
314 @cache_in_self
319315 def get_distributor(self) -> Distributor:
320 return self.distributor
321
316 return Distributor()
317
318 @cache_in_self
322319 def get_registration_ratelimiter(self) -> Ratelimiter:
323 return self.registration_ratelimiter
320 return Ratelimiter(
321 clock=self.get_clock(),
322 rate_hz=self.config.rc_registration.per_second,
323 burst_count=self.config.rc_registration.burst_count,
324 )
324325
325326 @cache_in_self
326327 def get_federation_client(self) -> FederationClient:
388389 return TypingWriterHandler(self)
389390 else:
390391 return FollowerTypingHandler(self)
392
393 @cache_in_self
394 def get_sso_handler(self) -> SsoHandler:
395 return SsoHandler(self)
391396
392397 @cache_in_self
393398 def get_sync_handler(self) -> SyncHandler:
680685
681686 @cache_in_self
682687 def get_federation_ratelimiter(self) -> FederationRateLimiter:
683 return FederationRateLimiter(self.clock, config=self.config.rc_federation)
688 return FederationRateLimiter(self.get_clock(), config=self.config.rc_federation)
684689
685690 @cache_in_self
686691 def get_module_api(self) -> ModuleApi:
3838 self._room_member_handler = hs.get_room_member_handler()
3939 self._event_creation_handler = hs.get_event_creation_handler()
4040 self._is_mine_id = hs.is_mine_id
41 self._server_name = hs.hostname
4142
4243 self._notifier = hs.get_notifier()
4344 self.server_notices_mxid = self._config.server_notices_mxid
7172 await self.maybe_invite_user_to_room(user_id, room_id)
7273
7374 system_mxid = self._config.server_notices_mxid
74 requester = create_requester(system_mxid)
75 requester = create_requester(
76 system_mxid, authenticated_entity=self._server_name
77 )
7578
7679 logger.info("Sending server notice to %s", user_id)
7780
144147 "avatar_url": self._config.server_notices_mxid_avatar_url,
145148 }
146149
147 requester = create_requester(self.server_notices_mxid)
150 requester = create_requester(
151 self.server_notices_mxid, authenticated_entity=self._server_name
152 )
148153 info, _ = await self._room_creation_handler.create_room(
149154 requester,
150155 config={
173178 user_id: The ID of the user to invite.
174179 room_id: The ID of the room to invite the user to.
175180 """
176 requester = create_requester(self.server_notices_mxid)
181 requester = create_requester(
182 self.server_notices_mxid, authenticated_entity=self._server_name
183 )
177184
178185 # Check whether the user has already joined or been invited to this room. If
179186 # that's the case, there is no need to re-invite them.
313313 for table in (
314314 "event_auth",
315315 "event_edges",
316 "event_json",
316317 "event_push_actions_staging",
317318 "event_reference_hashes",
318319 "event_relations",
339340 "destination_rooms",
340341 "event_backward_extremities",
341342 "event_forward_extremities",
342 "event_json",
343343 "event_push_actions",
344344 "event_search",
345345 "events",
277277 async def get_linearized_receipts_for_all_rooms(
278278 self, to_key: int, from_key: Optional[int] = None
279279 ) -> Dict[str, JsonDict]:
280 """Get receipts for all rooms between two stream_ids.
280 """Get receipts for all rooms between two stream_ids, up
281 to a limit of the latest 100 read receipts.
281282
282283 Args:
283284 to_key: Max stream id to fetch receipts upto.
293294 sql = """
294295 SELECT * FROM receipts_linearized WHERE
295296 stream_id > ? AND stream_id <= ?
297 ORDER BY stream_id DESC
298 LIMIT 100
296299 """
297300 txn.execute(sql, [from_key, to_key])
298301 else:
299302 sql = """
300303 SELECT * FROM receipts_linearized WHERE
301304 stream_id <= ?
305 ORDER BY stream_id DESC
306 LIMIT 100
302307 """
303308
304309 txn.execute(sql, [to_key])
11091109 token: str,
11101110 device_id: Optional[str],
11111111 valid_until_ms: Optional[int],
1112 puppets_user_id: Optional[str] = None,
11121113 ) -> int:
11131114 """Adds an access token for the given user.
11141115
11321133 "token": token,
11331134 "device_id": device_id,
11341135 "valid_until_ms": valid_until_ms,
1136 "puppets_user_id": puppets_user_id,
11351137 },
11361138 desc="add_access_token_to_user",
11371139 )
12391239 logger.error("store_room with room_id=%s failed: %s", room_id, e)
12401240 raise StoreError(500, "Problem creating room.")
12411241
1242 async def maybe_store_room_on_invite(self, room_id: str, room_version: RoomVersion):
1243 """
1244 When we receive an invite over federation, store the version of the room if we
1245 don't already know the room version.
1242 async def maybe_store_room_on_outlier_membership(
1243 self, room_id: str, room_version: RoomVersion
1244 ):
1245 """
1246 When we receive an invite or any other event over federation that may relate to a room
1247 we are not in, store the version of the room if we don't already know the room version.
12461248 """
12471249 await self.db_pool.simple_upsert(
1248 desc="maybe_store_room_on_invite",
1250 desc="maybe_store_room_on_outlier_membership",
12491251 table="rooms",
12501252 keyvalues={"room_id": room_id},
12511253 values={},
1313 # See the License for the specific language governing permissions and
1414 # limitations under the License.
1515 import logging
16 from typing import TYPE_CHECKING, Dict, FrozenSet, Iterable, List, Optional, Set
16 from typing import TYPE_CHECKING, Dict, FrozenSet, Iterable, List, Optional, Set, Tuple
1717
1818 from synapse.api.constants import EventTypes, Membership
1919 from synapse.events import EventBase
348348 results = [RoomsForUser(**r) for r in self.db_pool.cursor_to_dict(txn)]
349349
350350 return results
351
352 async def get_local_current_membership_for_user_in_room(
353 self, user_id: str, room_id: str
354 ) -> Tuple[Optional[str], Optional[str]]:
355 """Retrieve the current local membership state and event ID for a user in a room.
356
357 Args:
358 user_id: The ID of the user.
359 room_id: The ID of the room.
360
361 Returns:
362 A tuple of (membership_type, event_id). Both will be None if a
363 room_id/user_id pair is not found.
364 """
365 # Paranoia check.
366 if not self.hs.is_mine_id(user_id):
367 raise Exception(
368 "Cannot call 'get_local_current_membership_for_user_in_room' on "
369 "non-local user %s" % (user_id,),
370 )
371
372 results_dict = await self.db_pool.simple_select_one(
373 "local_current_membership",
374 {"room_id": room_id, "user_id": user_id},
375 ("membership", "event_id"),
376 allow_none=True,
377 desc="get_local_current_membership_for_user_in_room",
378 )
379 if not results_dict:
380 return None, None
381
382 return results_dict.get("membership"), results_dict.get("event_id")
351383
352384 @cached(max_entries=500000, iterable=True)
353385 async def get_rooms_for_user_with_stream_ordering(
1919 */
2020
2121 -- add new index that includes method to local media
22 INSERT INTO background_updates (update_name, progress_json) VALUES
23 ('local_media_repository_thumbnails_method_idx', '{}');
22 INSERT INTO background_updates (ordering, update_name, progress_json) VALUES
23 (5807, 'local_media_repository_thumbnails_method_idx', '{}');
2424
2525 -- add new index that includes method to remote media
26 INSERT INTO background_updates (update_name, progress_json, depends_on) VALUES
27 ('remote_media_repository_thumbnails_method_idx', '{}', 'local_media_repository_thumbnails_method_idx');
26 INSERT INTO background_updates (ordering, update_name, progress_json, depends_on) VALUES
27 (5807, 'remote_media_repository_thumbnails_method_idx', '{}', 'local_media_repository_thumbnails_method_idx');
2828
2929 -- drop old index
30 INSERT INTO background_updates (update_name, progress_json, depends_on) VALUES
31 ('media_repository_drop_index_wo_method', '{}', 'remote_media_repository_thumbnails_method_idx');
30 INSERT INTO background_updates (ordering, update_name, progress_json, depends_on) VALUES
31 (5807, 'media_repository_drop_index_wo_method', '{}', 'remote_media_repository_thumbnails_method_idx');
3232
2727 -- functionality as the old one. This effectively restarts the background job
2828 -- from the beginning, without running it twice in a row, supporting both
2929 -- upgrade usecases.
30 INSERT INTO background_updates (update_name, progress_json) VALUES
31 ('populate_stats_process_rooms_2', '{}');
30 INSERT INTO background_updates (ordering, update_name, progress_json) VALUES
31 (5812, 'populate_stats_process_rooms_2', '{}');
0 INSERT INTO background_updates (update_name, progress_json) VALUES
1 ('users_have_local_media', '{}');
0 INSERT INTO background_updates (ordering, update_name, progress_json) VALUES
1 (5822, 'users_have_local_media', '{}');
1212 * limitations under the License.
1313 */
1414
15 INSERT INTO background_updates (update_name, progress_json) VALUES
16 ('e2e_cross_signing_keys_idx', '{}');
15 INSERT INTO background_updates (ordering, update_name, progress_json) VALUES
16 (5823, 'e2e_cross_signing_keys_idx', '{}');
0 /* Copyright 2020 The Matrix.org Foundation C.I.C
1 *
2 * Licensed under the Apache License, Version 2.0 (the "License");
3 * you may not use this file except in compliance with the License.
4 * You may obtain a copy of the License at
5 *
6 * http://www.apache.org/licenses/LICENSE-2.0
7 *
8 * Unless required by applicable law or agreed to in writing, software
9 * distributed under the License is distributed on an "AS IS" BASIS,
10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and
12 * limitations under the License.
13 */
14
15 -- this index is essentially redundant. The only time it was ever used was when purging
16 -- rooms - and Synapse 1.24 will change that.
17
18 DROP INDEX IF EXISTS event_json_room_id;
316316 )
317317
318318
319 def contains_invalid_mxid_characters(localpart):
319 def contains_invalid_mxid_characters(localpart: str) -> bool:
320320 """Check for characters not allowed in an mxid or groupid localpart
321321
322322 Args:
323 localpart (basestring): the localpart to be checked
323 localpart: the localpart to be checked
324324
325325 Returns:
326 bool: True if there are any naughty characters
326 True if there are any naughty characters
327327 """
328328 return any(c not in mxid_localpart_allowed_characters for c in localpart)
329329
357357 for worker in workers:
358358 env = os.environ.copy()
359359
360 # Skip starting a worker if its already running
361 if os.path.exists(worker.pidfile) and pid_running(
362 int(open(worker.pidfile).read())
363 ):
364 print(worker.app + " already running")
365 continue
366
360367 if worker.cache_factor:
361368 os.environ["SYNAPSE_CACHE_FACTOR"] = str(worker.cache_factor)
362369
281281 )
282282 )
283283 self.store.add_access_token_to_user.assert_called_with(
284 USER_ID, token, "DEVICE", None
284 user_id=USER_ID,
285 token=token,
286 device_id="DEVICE",
287 valid_until_ms=None,
288 puppets_user_id=None,
285289 )
286290
287291 def get_user(tok):
1414
1515 from synapse.app.generic_worker import GenericWorkerServer
1616
17 from tests.server import make_request
1718 from tests.unittest import HomeserverTestCase
1819
1920
5455 # Grab the resource from the site that was told to listen
5556 self.assertEqual(len(self.reactor.tcpServers), 1)
5657 site = self.reactor.tcpServers[0][1]
57 self.resource = site.resource.children[b"_matrix"].children[b"client"]
5858
59 request, channel = self.make_request("PUT", "presence/a/status")
60 self.render(request)
59 _, channel = make_request(self.reactor, site, "PUT", "presence/a/status")
6160
6261 # 400 + unrecognised, because nothing is registered
6362 self.assertEqual(channel.code, 400)
7675 # Grab the resource from the site that was told to listen
7776 self.assertEqual(len(self.reactor.tcpServers), 1)
7877 site = self.reactor.tcpServers[0][1]
79 self.resource = site.resource.children[b"_matrix"].children[b"client"]
8078
81 request, channel = self.make_request("PUT", "presence/a/status")
82 self.render(request)
79 _, channel = make_request(self.reactor, site, "PUT", "presence/a/status")
8380
8481 # 401, because the stub servlet still checks authentication
8582 self.assertEqual(channel.code, 401)
1919 from synapse.app.homeserver import SynapseHomeServer
2020 from synapse.config.server import parse_listener_def
2121
22 from tests.server import make_request
2223 from tests.unittest import HomeserverTestCase
2324
2425
6566 # Grab the resource from the site that was told to listen
6667 site = self.reactor.tcpServers[0][1]
6768 try:
68 self.resource = site.resource.children[b"_matrix"].children[b"federation"]
69 site.resource.children[b"_matrix"].children[b"federation"]
6970 except KeyError:
7071 if expectation == "no_resource":
7172 return
7273 raise
7374
74 request, channel = self.make_request(
75 "GET", "/_matrix/federation/v1/openid/userinfo"
75 _, channel = make_request(
76 self.reactor, site, "GET", "/_matrix/federation/v1/openid/userinfo"
7677 )
77 self.render(request)
7878
7979 self.assertEqual(channel.code, 401)
8080
114114 # Grab the resource from the site that was told to listen
115115 site = self.reactor.tcpServers[0][1]
116116 try:
117 self.resource = site.resource.children[b"_matrix"].children[b"federation"]
117 site.resource.children[b"_matrix"].children[b"federation"]
118118 except KeyError:
119119 if expectation == "no_resource":
120120 return
121121 raise
122122
123 request, channel = self.make_request(
124 "GET", "/_matrix/federation/v1/openid/userinfo"
123 _, channel = make_request(
124 self.reactor, site, "GET", "/_matrix/federation/v1/openid/userinfo"
125125 )
126 self.render(request)
127126
128127 self.assertEqual(channel.code, 401)
5050 request, channel = self.make_request(
5151 "GET", "/_matrix/federation/unstable/rooms/%s/complexity" % (room_1,)
5252 )
53 self.render(request)
5453 self.assertEquals(200, channel.code)
5554 complexity = channel.json_body["v1"]
5655 self.assertTrue(complexity > 0, complexity)
6362 request, channel = self.make_request(
6463 "GET", "/_matrix/federation/unstable/rooms/%s/complexity" % (room_1,)
6564 )
66 self.render(request)
6765 self.assertEquals(200, channel.code)
6866 complexity = channel.json_body["v1"]
6967 self.assertEqual(complexity, 1.23)
5050 "/_matrix/federation/v1/get_missing_events/%s" % (room_1,),
5151 query_content,
5252 )
53 self.render(request)
5453 self.assertEquals(400, channel.code, channel.result)
5554 self.assertEqual(channel.json_body["errcode"], "M_NOT_JSON")
5655
9897 request, channel = self.make_request(
9998 "GET", "/_matrix/federation/v1/state/%s" % (room_1,)
10099 )
101 self.render(request)
102100 self.assertEquals(200, channel.code, channel.result)
103101
104102 self.assertEqual(
131129 request, channel = self.make_request(
132130 "GET", "/_matrix/federation/v1/state/%s" % (room_1,)
133131 )
134 self.render(request)
135132 self.assertEquals(403, channel.code, channel.result)
136133 self.assertEqual(channel.json_body["errcode"], "M_FORBIDDEN")
137134
3939 request, channel = self.make_request(
4040 "GET", "/_matrix/federation/v1/publicRooms"
4141 )
42 self.render(request)
4342 self.assertEquals(403, channel.code)
4443
4544 @override_config({"allow_public_rooms_over_federation": True})
4746 request, channel = self.make_request(
4847 "GET", "/_matrix/federation/v1/publicRooms"
4948 )
50 self.render(request)
5149 self.assertEquals(200, channel.code)
5151 self.fail("some_user was not in %s" % macaroon.inspect())
5252
5353 def test_macaroon_caveats(self):
54 self.hs.clock.now = 5000
54 self.hs.get_clock().now = 5000
5555
5656 token = self.macaroon_generator.generate_access_token("a_user")
5757 macaroon = pymacaroons.Macaroon.deserialize(token)
7777
7878 @defer.inlineCallbacks
7979 def test_short_term_login_token_gives_user_id(self):
80 self.hs.clock.now = 1000
80 self.hs.get_clock().now = 1000
8181
8282 token = self.macaroon_generator.generate_short_term_login_token("a_user", 5000)
8383 user_id = yield defer.ensureDeferred(
8686 self.assertEqual("a_user", user_id)
8787
8888 # when we advance the clock, the token should be rejected
89 self.hs.clock.now = 6000
89 self.hs.get_clock().now = 6000
9090 with self.assertRaises(synapse.api.errors.AuthError):
9191 yield defer.ensureDeferred(
9292 self.auth_handler.validate_short_term_login_token_and_get_user_id(token)
411411 b"directory/room/%23test%3Atest",
412412 ('{"room_id":"%s"}' % (room_id,)).encode("ascii"),
413413 )
414 self.render(request)
415414 self.assertEquals(403, channel.code, channel.result)
416415
417416 def test_allowed(self):
422421 b"directory/room/%23unofficial_test%3Atest",
423422 ('{"room_id":"%s"}' % (room_id,)).encode("ascii"),
424423 )
425 self.render(request)
426424 self.assertEquals(200, channel.code, channel.result)
427425
428426
437435 request, channel = self.make_request(
438436 "PUT", b"directory/list/room/%s" % (room_id.encode("ascii"),), b"{}"
439437 )
440 self.render(request)
441438 self.assertEquals(200, channel.code, channel.result)
442439
443440 self.room_list_handler = hs.get_room_list_handler()
451448
452449 # Room list is enabled so we should get some results
453450 request, channel = self.make_request("GET", b"publicRooms")
454 self.render(request)
455451 self.assertEquals(200, channel.code, channel.result)
456452 self.assertTrue(len(channel.json_body["chunk"]) > 0)
457453
460456
461457 # Room list disabled so we should get no results
462458 request, channel = self.make_request("GET", b"publicRooms")
463 self.render(request)
464459 self.assertEquals(200, channel.code, channel.result)
465460 self.assertTrue(len(channel.json_body["chunk"]) == 0)
466461
469464 request, channel = self.make_request(
470465 "PUT", b"directory/list/room/%s" % (room_id.encode("ascii"),), b"{}"
471466 )
472 self.render(request)
473467 self.assertEquals(403, channel.code, channel.result)
5858 )
5959
6060 d = self.handler.on_exchange_third_party_invite_request(
61 room_id=room_id,
6261 event_dict={
6362 "type": EventTypes.Member,
6463 "room_id": room_id,
208208 request, channel = self.make_request(
209209 "POST", path, content={}, access_token=self.access_token
210210 )
211 self.render(request)
212211 self.assertEqual(int(channel.result["code"]), 403)
1111 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212 # See the License for the specific language governing permissions and
1313 # limitations under the License.
14
1514 import json
1615 from urllib.parse import parse_qs, urlparse
1716
2322 from twisted.python.failure import Failure
2423 from twisted.web._newclient import ResponseDone
2524
26 from synapse.handlers.oidc_handler import (
27 MappingException,
28 OidcError,
29 OidcHandler,
30 OidcMappingProvider,
31 )
25 from synapse.handlers.oidc_handler import OidcError, OidcMappingProvider
26 from synapse.handlers.sso import MappingException
3227 from synapse.types import UserID
3328
3429 from tests.unittest import HomeserverTestCase, override_config
9186 class TestMappingProviderExtra(TestMappingProvider):
9287 async def get_extra_attributes(self, userinfo, token):
9388 return {"phone": userinfo["phone"]}
89
90
91 class TestMappingProviderFailures(TestMappingProvider):
92 async def map_user_attributes(self, userinfo, token, failures):
93 return {
94 "localpart": userinfo["username"] + (str(failures) if failures else ""),
95 "display_name": None,
96 }
9497
9598
9699 def simple_async_mock(return_value=None, raises=None):
123126
124127
125128 class OidcHandlerTestCase(HomeserverTestCase):
126 def make_homeserver(self, reactor, clock):
127
128 self.http_client = Mock(spec=["get_json"])
129 self.http_client.get_json.side_effect = get_json
130 self.http_client.user_agent = "Synapse Test"
131
132 config = self.default_config()
129 def default_config(self):
130 config = super().default_config()
133131 config["public_baseurl"] = BASE_URL
134 oidc_config = {}
135 oidc_config["enabled"] = True
136 oidc_config["client_id"] = CLIENT_ID
137 oidc_config["client_secret"] = CLIENT_SECRET
138 oidc_config["issuer"] = ISSUER
139 oidc_config["scopes"] = SCOPES
140 oidc_config["user_mapping_provider"] = {
141 "module": __name__ + ".TestMappingProvider",
132 oidc_config = {
133 "enabled": True,
134 "client_id": CLIENT_ID,
135 "client_secret": CLIENT_SECRET,
136 "issuer": ISSUER,
137 "scopes": SCOPES,
138 "user_mapping_provider": {"module": __name__ + ".TestMappingProvider"},
142139 }
143140
144141 # Update this config with what's in the default config so that
146143 oidc_config.update(config.get("oidc_config", {}))
147144 config["oidc_config"] = oidc_config
148145
149 hs = self.setup_test_homeserver(
150 http_client=self.http_client,
151 proxied_http_client=self.http_client,
152 config=config,
153 )
154
155 self.handler = OidcHandler(hs)
146 return config
147
148 def make_homeserver(self, reactor, clock):
149
150 self.http_client = Mock(spec=["get_json"])
151 self.http_client.get_json.side_effect = get_json
152 self.http_client.user_agent = "Synapse Test"
153
154 hs = self.setup_test_homeserver(proxied_http_client=self.http_client)
155
156 self.handler = hs.get_oidc_handler()
157 sso_handler = hs.get_sso_handler()
158 # Mock the render error method.
159 self.render_error = Mock(return_value=None)
160 sso_handler.render_error = self.render_error
161
162 # Reduce the number of attempts when generating MXIDs.
163 sso_handler._MAP_USERNAME_RETRIES = 3
156164
157165 return hs
158166
160168 return patch.dict(self.handler._provider_metadata, values)
161169
162170 def assertRenderedError(self, error, error_description=None):
163 args = self.handler._render_error.call_args[0]
171 args = self.render_error.call_args[0]
164172 self.assertEqual(args[1], error)
165173 if error_description is not None:
166174 self.assertEqual(args[2], error_description)
167175 # Reset the render_error mock
168 self.handler._render_error.reset_mock()
176 self.render_error.reset_mock()
169177
170178 def test_config(self):
171179 """Basic config correctly sets up the callback URL and client auth correctly."""
355363
356364 def test_callback_error(self):
357365 """Errors from the provider returned in the callback are displayed."""
358 self.handler._render_error = Mock()
359366 request = Mock(args={})
360367 request.args[b"error"] = [b"invalid_client"]
361368 self.get_success(self.handler.handle_oidc_callback(request))
386393 "preferred_username": "bar",
387394 }
388395 user_id = "@foo:domain.org"
389 self.handler._render_error = Mock(return_value=None)
390396 self.handler._exchange_code = simple_async_mock(return_value=token)
391397 self.handler._parse_id_token = simple_async_mock(return_value=userinfo)
392398 self.handler._fetch_userinfo = simple_async_mock(return_value=userinfo)
434440 userinfo, token, user_agent, ip_address
435441 )
436442 self.handler._fetch_userinfo.assert_not_called()
437 self.handler._render_error.assert_not_called()
443 self.render_error.assert_not_called()
438444
439445 # Handle mapping errors
440446 self.handler._map_userinfo_to_user = simple_async_mock(
468474 userinfo, token, user_agent, ip_address
469475 )
470476 self.handler._fetch_userinfo.assert_called_once_with(token)
471 self.handler._render_error.assert_not_called()
477 self.render_error.assert_not_called()
472478
473479 # Handle userinfo fetching error
474480 self.handler._fetch_userinfo = simple_async_mock(raises=Exception())
484490
485491 def test_callback_session(self):
486492 """The callback verifies the session presence and validity"""
487 self.handler._render_error = Mock(return_value=None)
488493 request = Mock(spec=["args", "getCookie", "addCookie"])
489494
490495 # Missing cookie
698703 ),
699704 MappingException,
700705 )
701 self.assertEqual(str(e.value), "mxid '@test_user_3:test' is already taken")
706 self.assertEqual(
707 str(e.value), "Mapping provider does not support de-duplicating Matrix IDs",
708 )
702709
703710 @override_config({"oidc_config": {"allow_existing_users": True}})
704711 def test_map_userinfo_to_existing_user(self):
705712 """Existing users can log in with OpenID Connect when allow_existing_users is True."""
706713 store = self.hs.get_datastore()
707 user4 = UserID.from_string("@test_user_4:test")
714 user = UserID.from_string("@test_user:test")
708715 self.get_success(
709 store.register_user(user_id=user4.to_string(), password_hash=None)
710 )
711 userinfo = {
712 "sub": "test4",
713 "username": "test_user_4",
716 store.register_user(user_id=user.to_string(), password_hash=None)
717 )
718
719 # Map a user via SSO.
720 userinfo = {
721 "sub": "test",
722 "username": "test_user",
714723 }
715724 token = {}
716725 mxid = self.get_success(
718727 userinfo, token, "user-agent", "10.10.10.10"
719728 )
720729 )
721 self.assertEqual(mxid, "@test_user_4:test")
730 self.assertEqual(mxid, "@test_user:test")
731
732 # Subsequent calls should map to the same mxid.
733 mxid = self.get_success(
734 self.handler._map_userinfo_to_user(
735 userinfo, token, "user-agent", "10.10.10.10"
736 )
737 )
738 self.assertEqual(mxid, "@test_user:test")
739
740 # Note that a second SSO user can be mapped to the same Matrix ID. (This
741 # requires a unique sub, but something that maps to the same matrix ID,
742 # in this case we'll just use the same username. A more realistic example
743 # would be subs which are email addresses, and mapping from the localpart
744 # of the email, e.g. bob@foo.com and bob@bar.com -> @bob:test.)
745 userinfo = {
746 "sub": "test1",
747 "username": "test_user",
748 }
749 token = {}
750 mxid = self.get_success(
751 self.handler._map_userinfo_to_user(
752 userinfo, token, "user-agent", "10.10.10.10"
753 )
754 )
755 self.assertEqual(mxid, "@test_user:test")
756
757 # Register some non-exact matching cases.
758 user2 = UserID.from_string("@TEST_user_2:test")
759 self.get_success(
760 store.register_user(user_id=user2.to_string(), password_hash=None)
761 )
762 user2_caps = UserID.from_string("@test_USER_2:test")
763 self.get_success(
764 store.register_user(user_id=user2_caps.to_string(), password_hash=None)
765 )
766
767 # Attempting to login without matching a name exactly is an error.
768 userinfo = {
769 "sub": "test2",
770 "username": "TEST_USER_2",
771 }
772 e = self.get_failure(
773 self.handler._map_userinfo_to_user(
774 userinfo, token, "user-agent", "10.10.10.10"
775 ),
776 MappingException,
777 )
778 self.assertTrue(
779 str(e.value).startswith(
780 "Attempted to login as '@TEST_USER_2:test' but it matches more than one user inexactly:"
781 )
782 )
783
784 # Logging in when matching a name exactly should work.
785 user2 = UserID.from_string("@TEST_USER_2:test")
786 self.get_success(
787 store.register_user(user_id=user2.to_string(), password_hash=None)
788 )
789
790 mxid = self.get_success(
791 self.handler._map_userinfo_to_user(
792 userinfo, token, "user-agent", "10.10.10.10"
793 )
794 )
795 self.assertEqual(mxid, "@TEST_USER_2:test")
796
797 def test_map_userinfo_to_invalid_localpart(self):
798 """If the mapping provider generates an invalid localpart it should be rejected."""
799 userinfo = {
800 "sub": "test2",
801 "username": "föö",
802 }
803 token = {}
804
805 e = self.get_failure(
806 self.handler._map_userinfo_to_user(
807 userinfo, token, "user-agent", "10.10.10.10"
808 ),
809 MappingException,
810 )
811 self.assertEqual(str(e.value), "localpart is invalid: föö")
812
813 @override_config(
814 {
815 "oidc_config": {
816 "user_mapping_provider": {
817 "module": __name__ + ".TestMappingProviderFailures"
818 }
819 }
820 }
821 )
822 def test_map_userinfo_to_user_retries(self):
823 """The mapping provider can retry generating an MXID if the MXID is already in use."""
824 store = self.hs.get_datastore()
825 self.get_success(
826 store.register_user(user_id="@test_user:test", password_hash=None)
827 )
828 userinfo = {
829 "sub": "test",
830 "username": "test_user",
831 }
832 token = {}
833 mxid = self.get_success(
834 self.handler._map_userinfo_to_user(
835 userinfo, token, "user-agent", "10.10.10.10"
836 )
837 )
838 # test_user is already taken, so test_user1 gets registered instead.
839 self.assertEqual(mxid, "@test_user1:test")
840
841 # Register all of the potential mxids for a particular OIDC username.
842 self.get_success(
843 store.register_user(user_id="@tester:test", password_hash=None)
844 )
845 for i in range(1, 3):
846 self.get_success(
847 store.register_user(user_id="@tester%d:test" % i, password_hash=None)
848 )
849
850 # Now attempt to map to a username, this will fail since all potential usernames are taken.
851 userinfo = {
852 "sub": "tester",
853 "username": "tester",
854 }
855 e = self.get_failure(
856 self.handler._map_userinfo_to_user(
857 userinfo, token, "user-agent", "10.10.10.10"
858 ),
859 MappingException,
860 )
861 self.assertEqual(
862 str(e.value), "Unable to generate a Matrix ID from the SSO response"
863 )
0 # -*- coding: utf-8 -*-
1 # Copyright 2020 The Matrix.org Foundation C.I.C.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 """Tests for the password_auth_provider interface"""
16
17 from typing import Any, Type, Union
18
19 from mock import Mock
20
21 from twisted.internet import defer
22
23 import synapse
24 from synapse.rest.client.v1 import login
25 from synapse.rest.client.v2_alpha import devices
26 from synapse.types import JsonDict
27
28 from tests import unittest
29 from tests.server import FakeChannel
30 from tests.unittest import override_config
31
32 # (possibly experimental) login flows we expect to appear in the list after the normal
33 # ones
34 ADDITIONAL_LOGIN_FLOWS = [{"type": "uk.half-shot.msc2778.login.application_service"}]
35
36 # a mock instance which the dummy auth providers delegate to, so we can see what's going
37 # on
38 mock_password_provider = Mock()
39
40
41 class PasswordOnlyAuthProvider:
42 """A password_provider which only implements `check_password`."""
43
44 @staticmethod
45 def parse_config(self):
46 pass
47
48 def __init__(self, config, account_handler):
49 pass
50
51 def check_password(self, *args):
52 return mock_password_provider.check_password(*args)
53
54
55 class CustomAuthProvider:
56 """A password_provider which implements a custom login type."""
57
58 @staticmethod
59 def parse_config(self):
60 pass
61
62 def __init__(self, config, account_handler):
63 pass
64
65 def get_supported_login_types(self):
66 return {"test.login_type": ["test_field"]}
67
68 def check_auth(self, *args):
69 return mock_password_provider.check_auth(*args)
70
71
72 class PasswordCustomAuthProvider:
73 """A password_provider which implements password login via `check_auth`, as well
74 as a custom type."""
75
76 @staticmethod
77 def parse_config(self):
78 pass
79
80 def __init__(self, config, account_handler):
81 pass
82
83 def get_supported_login_types(self):
84 return {"m.login.password": ["password"], "test.login_type": ["test_field"]}
85
86 def check_auth(self, *args):
87 return mock_password_provider.check_auth(*args)
88
89
90 def providers_config(*providers: Type[Any]) -> dict:
91 """Returns a config dict that will enable the given password auth providers"""
92 return {
93 "password_providers": [
94 {"module": "%s.%s" % (__name__, provider.__qualname__), "config": {}}
95 for provider in providers
96 ]
97 }
98
99
100 class PasswordAuthProviderTests(unittest.HomeserverTestCase):
101 servlets = [
102 synapse.rest.admin.register_servlets,
103 login.register_servlets,
104 devices.register_servlets,
105 ]
106
107 def setUp(self):
108 # we use a global mock device, so make sure we are starting with a clean slate
109 mock_password_provider.reset_mock()
110 super().setUp()
111
112 @override_config(providers_config(PasswordOnlyAuthProvider))
113 def test_password_only_auth_provider_login(self):
114 # login flows should only have m.login.password
115 flows = self._get_login_flows()
116 self.assertEqual(flows, [{"type": "m.login.password"}] + ADDITIONAL_LOGIN_FLOWS)
117
118 # check_password must return an awaitable
119 mock_password_provider.check_password.return_value = defer.succeed(True)
120 channel = self._send_password_login("u", "p")
121 self.assertEqual(channel.code, 200, channel.result)
122 self.assertEqual("@u:test", channel.json_body["user_id"])
123 mock_password_provider.check_password.assert_called_once_with("@u:test", "p")
124 mock_password_provider.reset_mock()
125
126 # login with mxid should work too
127 channel = self._send_password_login("@u:bz", "p")
128 self.assertEqual(channel.code, 200, channel.result)
129 self.assertEqual("@u:bz", channel.json_body["user_id"])
130 mock_password_provider.check_password.assert_called_once_with("@u:bz", "p")
131 mock_password_provider.reset_mock()
132
133 # try a weird username / pass. Honestly it's unclear what we *expect* to happen
134 # in these cases, but at least we can guard against the API changing
135 # unexpectedly
136 channel = self._send_password_login(" USER🙂NAME ", " pASS\U0001F622word ")
137 self.assertEqual(channel.code, 200, channel.result)
138 self.assertEqual("@ USER🙂NAME :test", channel.json_body["user_id"])
139 mock_password_provider.check_password.assert_called_once_with(
140 "@ USER🙂NAME :test", " pASS😢word "
141 )
142
143 @override_config(providers_config(PasswordOnlyAuthProvider))
144 def test_password_only_auth_provider_ui_auth(self):
145 """UI Auth should delegate correctly to the password provider"""
146
147 # create the user, otherwise access doesn't work
148 module_api = self.hs.get_module_api()
149 self.get_success(module_api.register_user("u"))
150
151 # log in twice, to get two devices
152 mock_password_provider.check_password.return_value = defer.succeed(True)
153 tok1 = self.login("u", "p")
154 self.login("u", "p", device_id="dev2")
155 mock_password_provider.reset_mock()
156
157 # have the auth provider deny the request to start with
158 mock_password_provider.check_password.return_value = defer.succeed(False)
159
160 # make the initial request which returns a 401
161 session = self._start_delete_device_session(tok1, "dev2")
162 mock_password_provider.check_password.assert_not_called()
163
164 # Make another request providing the UI auth flow.
165 channel = self._authed_delete_device(tok1, "dev2", session, "u", "p")
166 self.assertEqual(channel.code, 401) # XXX why not a 403?
167 self.assertEqual(channel.json_body["errcode"], "M_FORBIDDEN")
168 mock_password_provider.check_password.assert_called_once_with("@u:test", "p")
169 mock_password_provider.reset_mock()
170
171 # Finally, check the request goes through when we allow it
172 mock_password_provider.check_password.return_value = defer.succeed(True)
173 channel = self._authed_delete_device(tok1, "dev2", session, "u", "p")
174 self.assertEqual(channel.code, 200)
175 mock_password_provider.check_password.assert_called_once_with("@u:test", "p")
176
177 @override_config(providers_config(PasswordOnlyAuthProvider))
178 def test_local_user_fallback_login(self):
179 """rejected login should fall back to local db"""
180 self.register_user("localuser", "localpass")
181
182 # check_password must return an awaitable
183 mock_password_provider.check_password.return_value = defer.succeed(False)
184 channel = self._send_password_login("u", "p")
185 self.assertEqual(channel.code, 403, channel.result)
186
187 channel = self._send_password_login("localuser", "localpass")
188 self.assertEqual(channel.code, 200, channel.result)
189 self.assertEqual("@localuser:test", channel.json_body["user_id"])
190
191 @override_config(providers_config(PasswordOnlyAuthProvider))
192 def test_local_user_fallback_ui_auth(self):
193 """rejected login should fall back to local db"""
194 self.register_user("localuser", "localpass")
195
196 # have the auth provider deny the request
197 mock_password_provider.check_password.return_value = defer.succeed(False)
198
199 # log in twice, to get two devices
200 tok1 = self.login("localuser", "localpass")
201 self.login("localuser", "localpass", device_id="dev2")
202 mock_password_provider.check_password.reset_mock()
203
204 # first delete should give a 401
205 session = self._start_delete_device_session(tok1, "dev2")
206 mock_password_provider.check_password.assert_not_called()
207
208 # Wrong password
209 channel = self._authed_delete_device(tok1, "dev2", session, "localuser", "xxx")
210 self.assertEqual(channel.code, 401) # XXX why not a 403?
211 self.assertEqual(channel.json_body["errcode"], "M_FORBIDDEN")
212 mock_password_provider.check_password.assert_called_once_with(
213 "@localuser:test", "xxx"
214 )
215 mock_password_provider.reset_mock()
216
217 # Right password
218 channel = self._authed_delete_device(
219 tok1, "dev2", session, "localuser", "localpass"
220 )
221 self.assertEqual(channel.code, 200)
222 mock_password_provider.check_password.assert_called_once_with(
223 "@localuser:test", "localpass"
224 )
225
226 @override_config(
227 {
228 **providers_config(PasswordOnlyAuthProvider),
229 "password_config": {"localdb_enabled": False},
230 }
231 )
232 def test_no_local_user_fallback_login(self):
233 """localdb_enabled can block login with the local password
234 """
235 self.register_user("localuser", "localpass")
236
237 # check_password must return an awaitable
238 mock_password_provider.check_password.return_value = defer.succeed(False)
239 channel = self._send_password_login("localuser", "localpass")
240 self.assertEqual(channel.code, 403)
241 self.assertEqual(channel.json_body["errcode"], "M_FORBIDDEN")
242 mock_password_provider.check_password.assert_called_once_with(
243 "@localuser:test", "localpass"
244 )
245
246 @override_config(
247 {
248 **providers_config(PasswordOnlyAuthProvider),
249 "password_config": {"localdb_enabled": False},
250 }
251 )
252 def test_no_local_user_fallback_ui_auth(self):
253 """localdb_enabled can block ui auth with the local password
254 """
255 self.register_user("localuser", "localpass")
256
257 # allow login via the auth provider
258 mock_password_provider.check_password.return_value = defer.succeed(True)
259
260 # log in twice, to get two devices
261 tok1 = self.login("localuser", "p")
262 self.login("localuser", "p", device_id="dev2")
263 mock_password_provider.check_password.reset_mock()
264
265 # first delete should give a 401
266 channel = self._delete_device(tok1, "dev2")
267 self.assertEqual(channel.code, 401)
268 # m.login.password UIA is permitted because the auth provider allows it,
269 # even though the localdb does not.
270 self.assertEqual(channel.json_body["flows"], [{"stages": ["m.login.password"]}])
271 session = channel.json_body["session"]
272 mock_password_provider.check_password.assert_not_called()
273
274 # now try deleting with the local password
275 mock_password_provider.check_password.return_value = defer.succeed(False)
276 channel = self._authed_delete_device(
277 tok1, "dev2", session, "localuser", "localpass"
278 )
279 self.assertEqual(channel.code, 401) # XXX why not a 403?
280 self.assertEqual(channel.json_body["errcode"], "M_FORBIDDEN")
281 mock_password_provider.check_password.assert_called_once_with(
282 "@localuser:test", "localpass"
283 )
284
285 @override_config(
286 {
287 **providers_config(PasswordOnlyAuthProvider),
288 "password_config": {"enabled": False},
289 }
290 )
291 def test_password_auth_disabled(self):
292 """password auth doesn't work if it's disabled across the board"""
293 # login flows should be empty
294 flows = self._get_login_flows()
295 self.assertEqual(flows, ADDITIONAL_LOGIN_FLOWS)
296
297 # login shouldn't work and should be rejected with a 400 ("unknown login type")
298 channel = self._send_password_login("u", "p")
299 self.assertEqual(channel.code, 400, channel.result)
300 mock_password_provider.check_password.assert_not_called()
301
302 @override_config(providers_config(CustomAuthProvider))
303 def test_custom_auth_provider_login(self):
304 # login flows should have the custom flow and m.login.password, since we
305 # haven't disabled local password lookup.
306 # (password must come first, because reasons)
307 flows = self._get_login_flows()
308 self.assertEqual(
309 flows,
310 [{"type": "m.login.password"}, {"type": "test.login_type"}]
311 + ADDITIONAL_LOGIN_FLOWS,
312 )
313
314 # login with missing param should be rejected
315 channel = self._send_login("test.login_type", "u")
316 self.assertEqual(channel.code, 400, channel.result)
317 mock_password_provider.check_auth.assert_not_called()
318
319 mock_password_provider.check_auth.return_value = defer.succeed("@user:bz")
320 channel = self._send_login("test.login_type", "u", test_field="y")
321 self.assertEqual(channel.code, 200, channel.result)
322 self.assertEqual("@user:bz", channel.json_body["user_id"])
323 mock_password_provider.check_auth.assert_called_once_with(
324 "u", "test.login_type", {"test_field": "y"}
325 )
326 mock_password_provider.reset_mock()
327
328 # try a weird username. Again, it's unclear what we *expect* to happen
329 # in these cases, but at least we can guard against the API changing
330 # unexpectedly
331 mock_password_provider.check_auth.return_value = defer.succeed(
332 "@ MALFORMED! :bz"
333 )
334 channel = self._send_login("test.login_type", " USER🙂NAME ", test_field=" abc ")
335 self.assertEqual(channel.code, 200, channel.result)
336 self.assertEqual("@ MALFORMED! :bz", channel.json_body["user_id"])
337 mock_password_provider.check_auth.assert_called_once_with(
338 " USER🙂NAME ", "test.login_type", {"test_field": " abc "}
339 )
340
341 @override_config(providers_config(CustomAuthProvider))
342 def test_custom_auth_provider_ui_auth(self):
343 # register the user and log in twice, to get two devices
344 self.register_user("localuser", "localpass")
345 tok1 = self.login("localuser", "localpass")
346 self.login("localuser", "localpass", device_id="dev2")
347
348 # make the initial request which returns a 401
349 channel = self._delete_device(tok1, "dev2")
350 self.assertEqual(channel.code, 401)
351 # Ensure that flows are what is expected.
352 self.assertIn({"stages": ["m.login.password"]}, channel.json_body["flows"])
353 self.assertIn({"stages": ["test.login_type"]}, channel.json_body["flows"])
354 session = channel.json_body["session"]
355
356 # missing param
357 body = {
358 "auth": {
359 "type": "test.login_type",
360 "identifier": {"type": "m.id.user", "user": "localuser"},
361 "session": session,
362 },
363 }
364
365 channel = self._delete_device(tok1, "dev2", body)
366 self.assertEqual(channel.code, 400)
367 # there's a perfectly good M_MISSING_PARAM errcode, but heaven forfend we should
368 # use it...
369 self.assertIn("Missing parameters", channel.json_body["error"])
370 mock_password_provider.check_auth.assert_not_called()
371 mock_password_provider.reset_mock()
372
373 # right params, but authing as the wrong user
374 mock_password_provider.check_auth.return_value = defer.succeed("@user:bz")
375 body["auth"]["test_field"] = "foo"
376 channel = self._delete_device(tok1, "dev2", body)
377 self.assertEqual(channel.code, 403)
378 self.assertEqual(channel.json_body["errcode"], "M_FORBIDDEN")
379 mock_password_provider.check_auth.assert_called_once_with(
380 "localuser", "test.login_type", {"test_field": "foo"}
381 )
382 mock_password_provider.reset_mock()
383
384 # and finally, succeed
385 mock_password_provider.check_auth.return_value = defer.succeed(
386 "@localuser:test"
387 )
388 channel = self._delete_device(tok1, "dev2", body)
389 self.assertEqual(channel.code, 200)
390 mock_password_provider.check_auth.assert_called_once_with(
391 "localuser", "test.login_type", {"test_field": "foo"}
392 )
393
394 @override_config(providers_config(CustomAuthProvider))
395 def test_custom_auth_provider_callback(self):
396 callback = Mock(return_value=defer.succeed(None))
397
398 mock_password_provider.check_auth.return_value = defer.succeed(
399 ("@user:bz", callback)
400 )
401 channel = self._send_login("test.login_type", "u", test_field="y")
402 self.assertEqual(channel.code, 200, channel.result)
403 self.assertEqual("@user:bz", channel.json_body["user_id"])
404 mock_password_provider.check_auth.assert_called_once_with(
405 "u", "test.login_type", {"test_field": "y"}
406 )
407
408 # check the args to the callback
409 callback.assert_called_once()
410 call_args, call_kwargs = callback.call_args
411 # should be one positional arg
412 self.assertEqual(len(call_args), 1)
413 self.assertEqual(call_args[0]["user_id"], "@user:bz")
414 for p in ["user_id", "access_token", "device_id", "home_server"]:
415 self.assertIn(p, call_args[0])
416
417 @override_config(
418 {**providers_config(CustomAuthProvider), "password_config": {"enabled": False}}
419 )
420 def test_custom_auth_password_disabled(self):
421 """Test login with a custom auth provider where password login is disabled"""
422 self.register_user("localuser", "localpass")
423
424 flows = self._get_login_flows()
425 self.assertEqual(flows, [{"type": "test.login_type"}] + ADDITIONAL_LOGIN_FLOWS)
426
427 # login shouldn't work and should be rejected with a 400 ("unknown login type")
428 channel = self._send_password_login("localuser", "localpass")
429 self.assertEqual(channel.code, 400, channel.result)
430 mock_password_provider.check_auth.assert_not_called()
431
432 @override_config(
433 {
434 **providers_config(PasswordCustomAuthProvider),
435 "password_config": {"enabled": False},
436 }
437 )
438 def test_password_custom_auth_password_disabled_login(self):
439 """log in with a custom auth provider which implements password, but password
440 login is disabled"""
441 self.register_user("localuser", "localpass")
442
443 flows = self._get_login_flows()
444 self.assertEqual(flows, [{"type": "test.login_type"}] + ADDITIONAL_LOGIN_FLOWS)
445
446 # login shouldn't work and should be rejected with a 400 ("unknown login type")
447 channel = self._send_password_login("localuser", "localpass")
448 self.assertEqual(channel.code, 400, channel.result)
449 mock_password_provider.check_auth.assert_not_called()
450
451 @override_config(
452 {
453 **providers_config(PasswordCustomAuthProvider),
454 "password_config": {"enabled": False},
455 }
456 )
457 def test_password_custom_auth_password_disabled_ui_auth(self):
458 """UI Auth with a custom auth provider which implements password, but password
459 login is disabled"""
460 # register the user and log in twice via the test login type to get two devices,
461 self.register_user("localuser", "localpass")
462 mock_password_provider.check_auth.return_value = defer.succeed(
463 "@localuser:test"
464 )
465 channel = self._send_login("test.login_type", "localuser", test_field="")
466 self.assertEqual(channel.code, 200, channel.result)
467 tok1 = channel.json_body["access_token"]
468
469 channel = self._send_login(
470 "test.login_type", "localuser", test_field="", device_id="dev2"
471 )
472 self.assertEqual(channel.code, 200, channel.result)
473
474 # make the initial request which returns a 401
475 channel = self._delete_device(tok1, "dev2")
476 self.assertEqual(channel.code, 401)
477 # Ensure that flows are what is expected. In particular, "password" should *not*
478 # be present.
479 self.assertIn({"stages": ["test.login_type"]}, channel.json_body["flows"])
480 session = channel.json_body["session"]
481
482 mock_password_provider.reset_mock()
483
484 # check that auth with password is rejected
485 body = {
486 "auth": {
487 "type": "m.login.password",
488 "identifier": {"type": "m.id.user", "user": "localuser"},
489 "password": "localpass",
490 "session": session,
491 },
492 }
493
494 channel = self._delete_device(tok1, "dev2", body)
495 self.assertEqual(channel.code, 400)
496 self.assertEqual(
497 "Password login has been disabled.", channel.json_body["error"]
498 )
499 mock_password_provider.check_auth.assert_not_called()
500 mock_password_provider.reset_mock()
501
502 # successful auth
503 body["auth"]["type"] = "test.login_type"
504 body["auth"]["test_field"] = "x"
505 channel = self._delete_device(tok1, "dev2", body)
506 self.assertEqual(channel.code, 200)
507 mock_password_provider.check_auth.assert_called_once_with(
508 "localuser", "test.login_type", {"test_field": "x"}
509 )
510
511 @override_config(
512 {
513 **providers_config(CustomAuthProvider),
514 "password_config": {"localdb_enabled": False},
515 }
516 )
517 def test_custom_auth_no_local_user_fallback(self):
518 """Test login with a custom auth provider where the local db is disabled"""
519 self.register_user("localuser", "localpass")
520
521 flows = self._get_login_flows()
522 self.assertEqual(flows, [{"type": "test.login_type"}] + ADDITIONAL_LOGIN_FLOWS)
523
524 # password login shouldn't work and should be rejected with a 400
525 # ("unknown login type")
526 channel = self._send_password_login("localuser", "localpass")
527 self.assertEqual(channel.code, 400, channel.result)
528
529 def _get_login_flows(self) -> JsonDict:
530 _, channel = self.make_request("GET", "/_matrix/client/r0/login")
531 self.assertEqual(channel.code, 200, channel.result)
532 return channel.json_body["flows"]
533
534 def _send_password_login(self, user: str, password: str) -> FakeChannel:
535 return self._send_login(type="m.login.password", user=user, password=password)
536
537 def _send_login(self, type, user, **params) -> FakeChannel:
538 params.update({"identifier": {"type": "m.id.user", "user": user}, "type": type})
539 _, channel = self.make_request("POST", "/_matrix/client/r0/login", params)
540 return channel
541
542 def _start_delete_device_session(self, access_token, device_id) -> str:
543 """Make an initial delete device request, and return the UI Auth session ID"""
544 channel = self._delete_device(access_token, device_id)
545 self.assertEqual(channel.code, 401)
546 # Ensure that flows are what is expected.
547 self.assertIn({"stages": ["m.login.password"]}, channel.json_body["flows"])
548 return channel.json_body["session"]
549
550 def _authed_delete_device(
551 self,
552 access_token: str,
553 device_id: str,
554 session: str,
555 user_id: str,
556 password: str,
557 ) -> FakeChannel:
558 """Make a delete device request, authenticating with the given uid/password"""
559 return self._delete_device(
560 access_token,
561 device_id,
562 {
563 "auth": {
564 "type": "m.login.password",
565 "identifier": {"type": "m.id.user", "user": user_id},
566 "password": password,
567 "session": session,
568 },
569 },
570 )
571
572 def _delete_device(
573 self, access_token: str, device: str, body: Union[JsonDict, bytes] = b"",
574 ) -> FakeChannel:
575 """Delete an individual device."""
576 _, channel = self.make_request(
577 "DELETE", "devices/" + device, body, access_token=access_token
578 )
579 return channel
0 # Copyright 2020 The Matrix.org Foundation C.I.C.
1 #
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at
5 #
6 # http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 import attr
15
16 from synapse.api.errors import RedirectException
17 from synapse.handlers.sso import MappingException
18
19 from tests.unittest import HomeserverTestCase, override_config
20
21 # These are a few constants that are used as config parameters in the tests.
22 BASE_URL = "https://synapse/"
23
24
25 @attr.s
26 class FakeAuthnResponse:
27 ava = attr.ib(type=dict)
28
29
30 class TestMappingProvider:
31 def __init__(self, config, module):
32 pass
33
34 @staticmethod
35 def parse_config(config):
36 return
37
38 @staticmethod
39 def get_saml_attributes(config):
40 return {"uid"}, {"displayName"}
41
42 def get_remote_user_id(self, saml_response, client_redirect_url):
43 return saml_response.ava["uid"]
44
45 def saml_response_to_user_attributes(
46 self, saml_response, failures, client_redirect_url
47 ):
48 localpart = saml_response.ava["username"] + (str(failures) if failures else "")
49 return {"mxid_localpart": localpart, "displayname": None}
50
51
52 class TestRedirectMappingProvider(TestMappingProvider):
53 def saml_response_to_user_attributes(
54 self, saml_response, failures, client_redirect_url
55 ):
56 raise RedirectException(b"https://custom-saml-redirect/")
57
58
59 class SamlHandlerTestCase(HomeserverTestCase):
60 def default_config(self):
61 config = super().default_config()
62 config["public_baseurl"] = BASE_URL
63 saml_config = {
64 "sp_config": {"metadata": {}},
65 # Disable grandfathering.
66 "grandfathered_mxid_source_attribute": None,
67 "user_mapping_provider": {"module": __name__ + ".TestMappingProvider"},
68 }
69
70 # Update this config with what's in the default config so that
71 # override_config works as expected.
72 saml_config.update(config.get("saml2_config", {}))
73 config["saml2_config"] = saml_config
74
75 return config
76
77 def make_homeserver(self, reactor, clock):
78 hs = self.setup_test_homeserver()
79
80 self.handler = hs.get_saml_handler()
81
82 # Reduce the number of attempts when generating MXIDs.
83 sso_handler = hs.get_sso_handler()
84 sso_handler._MAP_USERNAME_RETRIES = 3
85
86 return hs
87
88 def test_map_saml_response_to_user(self):
89 """Ensure that mapping the SAML response returned from a provider to an MXID works properly."""
90 saml_response = FakeAuthnResponse({"uid": "test_user", "username": "test_user"})
91 # The redirect_url doesn't matter with the default user mapping provider.
92 redirect_url = ""
93 mxid = self.get_success(
94 self.handler._map_saml_response_to_user(
95 saml_response, redirect_url, "user-agent", "10.10.10.10"
96 )
97 )
98 self.assertEqual(mxid, "@test_user:test")
99
100 @override_config({"saml2_config": {"grandfathered_mxid_source_attribute": "mxid"}})
101 def test_map_saml_response_to_existing_user(self):
102 """Existing users can log in with SAML account."""
103 store = self.hs.get_datastore()
104 self.get_success(
105 store.register_user(user_id="@test_user:test", password_hash=None)
106 )
107
108 # Map a user via SSO.
109 saml_response = FakeAuthnResponse(
110 {"uid": "tester", "mxid": ["test_user"], "username": "test_user"}
111 )
112 redirect_url = ""
113 mxid = self.get_success(
114 self.handler._map_saml_response_to_user(
115 saml_response, redirect_url, "user-agent", "10.10.10.10"
116 )
117 )
118 self.assertEqual(mxid, "@test_user:test")
119
120 # Subsequent calls should map to the same mxid.
121 mxid = self.get_success(
122 self.handler._map_saml_response_to_user(
123 saml_response, redirect_url, "user-agent", "10.10.10.10"
124 )
125 )
126 self.assertEqual(mxid, "@test_user:test")
127
128 def test_map_saml_response_to_invalid_localpart(self):
129 """If the mapping provider generates an invalid localpart it should be rejected."""
130 saml_response = FakeAuthnResponse({"uid": "test", "username": "föö"})
131 redirect_url = ""
132 e = self.get_failure(
133 self.handler._map_saml_response_to_user(
134 saml_response, redirect_url, "user-agent", "10.10.10.10"
135 ),
136 MappingException,
137 )
138 self.assertEqual(str(e.value), "localpart is invalid: föö")
139
140 def test_map_saml_response_to_user_retries(self):
141 """The mapping provider can retry generating an MXID if the MXID is already in use."""
142 store = self.hs.get_datastore()
143 self.get_success(
144 store.register_user(user_id="@test_user:test", password_hash=None)
145 )
146 saml_response = FakeAuthnResponse({"uid": "test", "username": "test_user"})
147 redirect_url = ""
148 mxid = self.get_success(
149 self.handler._map_saml_response_to_user(
150 saml_response, redirect_url, "user-agent", "10.10.10.10"
151 )
152 )
153 # test_user is already taken, so test_user1 gets registered instead.
154 self.assertEqual(mxid, "@test_user1:test")
155
156 # Register all of the potential mxids for a particular SAML username.
157 self.get_success(
158 store.register_user(user_id="@tester:test", password_hash=None)
159 )
160 for i in range(1, 3):
161 self.get_success(
162 store.register_user(user_id="@tester%d:test" % i, password_hash=None)
163 )
164
165 # Now attempt to map to a username, this will fail since all potential usernames are taken.
166 saml_response = FakeAuthnResponse({"uid": "tester", "username": "tester"})
167 e = self.get_failure(
168 self.handler._map_saml_response_to_user(
169 saml_response, redirect_url, "user-agent", "10.10.10.10"
170 ),
171 MappingException,
172 )
173 self.assertEqual(
174 str(e.value), "Unable to generate a Matrix ID from the SSO response"
175 )
176
177 @override_config(
178 {
179 "saml2_config": {
180 "user_mapping_provider": {
181 "module": __name__ + ".TestRedirectMappingProvider"
182 },
183 }
184 }
185 )
186 def test_map_saml_response_redirect(self):
187 saml_response = FakeAuthnResponse({"uid": "test", "username": "test_user"})
188 redirect_url = ""
189 e = self.get_failure(
190 self.handler._map_saml_response_to_user(
191 saml_response, redirect_url, "user-agent", "10.10.10.10"
192 ),
193 RedirectException,
194 )
195 self.assertEqual(e.value.location, b"https://custom-saml-redirect/")
1515 from synapse.api.errors import Codes, ResourceLimitError
1616 from synapse.api.filtering import DEFAULT_FILTER_COLLECTION
1717 from synapse.handlers.sync import SyncConfig
18 from synapse.types import UserID
18 from synapse.types import UserID, create_requester
1919
2020 import tests.unittest
2121 import tests.utils
3737 user_id1 = "@user1:test"
3838 user_id2 = "@user2:test"
3939 sync_config = self._generate_sync_config(user_id1)
40 requester = create_requester(user_id1)
4041
4142 self.reactor.advance(100) # So we get not 0 time
4243 self.auth_blocking._limit_usage_by_mau = True
4445
4546 # Check that the happy case does not throw errors
4647 self.get_success(self.store.upsert_monthly_active_user(user_id1))
47 self.get_success(self.sync_handler.wait_for_sync_for_user(sync_config))
48 self.get_success(
49 self.sync_handler.wait_for_sync_for_user(requester, sync_config)
50 )
4851
4952 # Test that global lock works
5053 self.auth_blocking._hs_disabled = True
5154 e = self.get_failure(
52 self.sync_handler.wait_for_sync_for_user(sync_config), ResourceLimitError
55 self.sync_handler.wait_for_sync_for_user(requester, sync_config),
56 ResourceLimitError,
5357 )
5458 self.assertEquals(e.value.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)
5559
5660 self.auth_blocking._hs_disabled = False
5761
5862 sync_config = self._generate_sync_config(user_id2)
63 requester = create_requester(user_id2)
5964
6065 e = self.get_failure(
61 self.sync_handler.wait_for_sync_for_user(sync_config), ResourceLimitError
66 self.sync_handler.wait_for_sync_for_user(requester, sync_config),
67 ResourceLimitError,
6268 )
6369 self.assertEquals(e.value.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)
6470
227227 ),
228228 federation_auth_origin=b"farm",
229229 )
230 self.render(request)
231230 self.assertEqual(channel.code, 200)
232231
233232 self.on_new_event.assert_has_calls([call("typing_key", 1, rooms=[ROOM_ID])])
536536 request, channel = self.make_request(
537537 "POST", b"user_directory/search", b'{"search_term":"user2"}'
538538 )
539 self.render(request)
540539 self.assertEquals(200, channel.code, channel.result)
541540 self.assertTrue(len(channel.json_body["results"]) > 0)
542541
545544 request, channel = self.make_request(
546545 "POST", b"user_directory/search", b'{"search_term":"user2"}'
547546 )
548 self.render(request)
549547 self.assertEquals(200, channel.code, channel.result)
550548 self.assertTrue(len(channel.json_body["results"]) == 0)
1616 from synapse.http.additional_resource import AdditionalResource
1717 from synapse.http.server import respond_with_json
1818
19 from tests.server import FakeSite, make_request
1920 from tests.unittest import HomeserverTestCase
2021
2122
4243
4344 def test_async(self):
4445 handler = _AsyncTestCustomEndpoint({}, None).handle_request
45 self.resource = AdditionalResource(self.hs, handler)
46 resource = AdditionalResource(self.hs, handler)
4647
47 request, channel = self.make_request("GET", "/")
48 self.render(request)
48 request, channel = make_request(self.reactor, FakeSite(resource), "GET", "/")
4949
5050 self.assertEqual(request.code, 200)
5151 self.assertEqual(channel.json_body, {"some_key": "some_value_async"})
5252
5353 def test_sync(self):
5454 handler = _SyncTestCustomEndpoint({}, None).handle_request
55 self.resource = AdditionalResource(self.hs, handler)
55 resource = AdditionalResource(self.hs, handler)
5656
57 request, channel = self.make_request("GET", "/")
58 self.render(request)
57 request, channel = make_request(self.reactor, FakeSite(resource), "GET", "/")
5958
6059 self.assertEqual(request.code, 200)
6160 self.assertEqual(channel.json_body, {"some_key": "some_value_sync"})
9393 self.assertFalse(hasattr(event, "state_key"))
9494 self.assertDictEqual(event.content, content)
9595
96 expected_requester = create_requester(
97 user_id, authenticated_entity=self.hs.hostname
98 )
99
96100 # Check that the event was sent
97101 self.event_creation_handler.create_and_send_nonmember_event.assert_called_with(
98 create_requester(user_id),
99 event_dict,
100 ratelimit=False,
101 ignore_shadow_ban=True,
102 expected_requester, event_dict, ratelimit=False, ignore_shadow_ban=True,
102103 )
103104
104105 # Create and send a state event
127128
128129 # Check that the event was sent
129130 self.event_creation_handler.create_and_send_nonmember_event.assert_called_with(
130 create_requester(user_id),
131 expected_requester,
131132 {
132133 "type": "m.room.power_levels",
133134 "content": content,
1111 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212 # See the License for the specific language governing permissions and
1313 # limitations under the License.
14
1514 from mock import Mock
1615
1716 from twisted.internet.defer import Deferred
1918 import synapse.rest.admin
2019 from synapse.logging.context import make_deferred_yieldable
2120 from synapse.rest.client.v1 import login, room
22
23 from tests.unittest import HomeserverTestCase
21 from synapse.rest.client.v2_alpha import receipts
22
23 from tests.unittest import HomeserverTestCase, override_config
2424
2525
2626 class HTTPPusherTests(HomeserverTestCase):
2828 synapse.rest.admin.register_servlets_for_client_rest_resource,
2929 room.register_servlets,
3030 login.register_servlets,
31 receipts.register_servlets,
3132 ]
3233 user_id = True
3334 hijack_auth = False
498499
499500 # check that this is low-priority
500501 self.assertEqual(self.push_attempts[1][2]["notification"]["prio"], "low")
502
503 def test_push_unread_count_group_by_room(self):
504 """
505 The HTTP pusher will group unread count by number of unread rooms.
506 """
507 # Carry out common push count tests and setup
508 self._test_push_unread_count()
509
510 # Carry out our option-value specific test
511 #
512 # This push should still only contain an unread count of 1 (for 1 unread room)
513 self.assertEqual(
514 self.push_attempts[5][2]["notification"]["counts"]["unread"], 1
515 )
516
517 @override_config({"push": {"group_unread_count_by_room": False}})
518 def test_push_unread_count_message_count(self):
519 """
520 The HTTP pusher will send the total unread message count.
521 """
522 # Carry out common push count tests and setup
523 self._test_push_unread_count()
524
525 # Carry out our option-value specific test
526 #
527 # We're counting every unread message, so there should now be 4 since the
528 # last read receipt
529 self.assertEqual(
530 self.push_attempts[5][2]["notification"]["counts"]["unread"], 4
531 )
532
533 def _test_push_unread_count(self):
534 """
535 Tests that the correct unread count appears in sent push notifications
536
537 Note that:
538 * Sending messages will cause push notifications to go out to relevant users
539 * Sending a read receipt will cause a "badge update" notification to go out to
540 the user that sent the receipt
541 """
542 # Register the user who gets notified
543 user_id = self.register_user("user", "pass")
544 access_token = self.login("user", "pass")
545
546 # Register the user who sends the message
547 other_user_id = self.register_user("other_user", "pass")
548 other_access_token = self.login("other_user", "pass")
549
550 # Create a room (as other_user)
551 room_id = self.helper.create_room_as(other_user_id, tok=other_access_token)
552
553 # The user to get notified joins
554 self.helper.join(room=room_id, user=user_id, tok=access_token)
555
556 # Register the pusher
557 user_tuple = self.get_success(
558 self.hs.get_datastore().get_user_by_access_token(access_token)
559 )
560 token_id = user_tuple.token_id
561
562 self.get_success(
563 self.hs.get_pusherpool().add_pusher(
564 user_id=user_id,
565 access_token=token_id,
566 kind="http",
567 app_id="m.http",
568 app_display_name="HTTP Push Notifications",
569 device_display_name="pushy push",
570 pushkey="a@example.com",
571 lang=None,
572 data={"url": "example.com"},
573 )
574 )
575
576 # Send a message
577 response = self.helper.send(
578 room_id, body="Hello there!", tok=other_access_token
579 )
580 # To get an unread count, the user who is getting notified has to have a read
581 # position in the room. We'll set the read position to this event in a moment
582 first_message_event_id = response["event_id"]
583
584 # Advance time a bit (so the pusher will register something has happened) and
585 # make the push succeed
586 self.push_attempts[0][0].callback({})
587 self.pump()
588
589 # Check our push made it
590 self.assertEqual(len(self.push_attempts), 1)
591 self.assertEqual(self.push_attempts[0][1], "example.com")
592
593 # Check that the unread count for the room is 0
594 #
595 # The unread count is zero as the user has no read receipt in the room yet
596 self.assertEqual(
597 self.push_attempts[0][2]["notification"]["counts"]["unread"], 0
598 )
599
600 # Now set the user's read receipt position to the first event
601 #
602 # This will actually trigger a new notification to be sent out so that
603 # even if the user does not receive another message, their unread
604 # count goes down
605 request, channel = self.make_request(
606 "POST",
607 "/rooms/%s/receipt/m.read/%s" % (room_id, first_message_event_id),
608 {},
609 access_token=access_token,
610 )
611 self.assertEqual(channel.code, 200, channel.json_body)
612
613 # Advance time and make the push succeed
614 self.push_attempts[1][0].callback({})
615 self.pump()
616
617 # Unread count is still zero as we've read the only message in the room
618 self.assertEqual(len(self.push_attempts), 2)
619 self.assertEqual(
620 self.push_attempts[1][2]["notification"]["counts"]["unread"], 0
621 )
622
623 # Send another message
624 self.helper.send(
625 room_id, body="How's the weather today?", tok=other_access_token
626 )
627
628 # Advance time and make the push succeed
629 self.push_attempts[2][0].callback({})
630 self.pump()
631
632 # This push should contain an unread count of 1 as there's now been one
633 # message since our last read receipt
634 self.assertEqual(len(self.push_attempts), 3)
635 self.assertEqual(
636 self.push_attempts[2][2]["notification"]["counts"]["unread"], 1
637 )
638
639 # Since we're grouping by room, sending more messages shouldn't increase the
640 # unread count, as they're all being sent in the same room
641 self.helper.send(room_id, body="Hello?", tok=other_access_token)
642
643 # Advance time and make the push succeed
644 self.pump()
645 self.push_attempts[3][0].callback({})
646
647 self.helper.send(room_id, body="Hello??", tok=other_access_token)
648
649 # Advance time and make the push succeed
650 self.pump()
651 self.push_attempts[4][0].callback({})
652
653 self.helper.send(room_id, body="HELLO???", tok=other_access_token)
654
655 # Advance time and make the push succeed
656 self.pump()
657 self.push_attempts[5][0].callback({})
658
659 self.assertEqual(len(self.push_attempts), 6)
3535 from synapse.util import Clock
3636
3737 from tests import unittest
38 from tests.server import FakeTransport, render
38 from tests.server import FakeTransport
3939
4040 try:
4141 import hiredis
7777 self.worker_hs.get_datastore().db_pool = hs.get_datastore().db_pool
7878
7979 self.test_handler = self._build_replication_data_handler()
80 self.worker_hs.replication_data_handler = self.test_handler
80 self.worker_hs._replication_data_handler = self.test_handler
8181
8282 repl_handler = ReplicationCommandHandler(self.worker_hs)
8383 self.client = ClientReplicationStreamProtocol(
239239 lambda: self._handle_http_replication_attempt(self.hs, 8765),
240240 )
241241
242 def create_test_json_resource(self):
243 """Overrides `HomeserverTestCase.create_test_json_resource`.
242 def create_test_resource(self):
243 """Overrides `HomeserverTestCase.create_test_resource`.
244244 """
245245 # We override this so that it automatically registers all the HTTP
246246 # replication servlets, without having to explicitly do that in all
346346 config["worker_replication_http_port"] = "8765"
347347 return config
348348
349 def render_on_worker(self, worker_hs: HomeServer, request: SynapseRequest):
350 render(request, self._hs_to_site[worker_hs].resource, self.reactor)
351
352349 def replicate(self):
353350 """Tell the master side of replication that something has happened, and then
354351 wait for the replication to occur.
1919
2020 from tests.replication._base import BaseMultiWorkerStreamTestCase
2121 from tests.rest.client.v2_alpha.test_auth import DummyRecaptchaChecker
22 from tests.server import FakeChannel
22 from tests.server import FakeChannel, make_request
2323
2424 logger = logging.getLogger(__name__)
2525
4545 """Test that registration works when using a single client reader worker.
4646 """
4747 worker_hs = self.make_worker_hs("synapse.app.client_reader")
48 site = self._hs_to_site[worker_hs]
4849
49 request_1, channel_1 = self.make_request(
50 request_1, channel_1 = make_request(
51 self.reactor,
52 site,
5053 "POST",
5154 "register",
5255 {"username": "user", "type": "m.login.password", "password": "bar"},
5356 ) # type: SynapseRequest, FakeChannel
54 self.render_on_worker(worker_hs, request_1)
5557 self.assertEqual(request_1.code, 401)
5658
5759 # Grab the session
5860 session = channel_1.json_body["session"]
5961
6062 # also complete the dummy auth
61 request_2, channel_2 = self.make_request(
62 "POST", "register", {"auth": {"session": session, "type": "m.login.dummy"}}
63 request_2, channel_2 = make_request(
64 self.reactor,
65 site,
66 "POST",
67 "register",
68 {"auth": {"session": session, "type": "m.login.dummy"}},
6369 ) # type: SynapseRequest, FakeChannel
64 self.render_on_worker(worker_hs, request_2)
6570 self.assertEqual(request_2.code, 200)
6671
6772 # We're given a registered user.
7378 worker_hs_1 = self.make_worker_hs("synapse.app.client_reader")
7479 worker_hs_2 = self.make_worker_hs("synapse.app.client_reader")
7580
76 request_1, channel_1 = self.make_request(
81 site_1 = self._hs_to_site[worker_hs_1]
82 request_1, channel_1 = make_request(
83 self.reactor,
84 site_1,
7785 "POST",
7886 "register",
7987 {"username": "user", "type": "m.login.password", "password": "bar"},
8088 ) # type: SynapseRequest, FakeChannel
81 self.render_on_worker(worker_hs_1, request_1)
8289 self.assertEqual(request_1.code, 401)
8390
8491 # Grab the session
8592 session = channel_1.json_body["session"]
8693
8794 # also complete the dummy auth
88 request_2, channel_2 = self.make_request(
89 "POST", "register", {"auth": {"session": session, "type": "m.login.dummy"}}
95 site_2 = self._hs_to_site[worker_hs_2]
96 request_2, channel_2 = make_request(
97 self.reactor,
98 site_2,
99 "POST",
100 "register",
101 {"auth": {"session": session, "type": "m.login.dummy"}},
90102 ) # type: SynapseRequest, FakeChannel
91 self.render_on_worker(worker_hs_2, request_2)
92103 self.assertEqual(request_2.code, 200)
93104
94105 # We're given a registered user.
2727
2828 from tests.http import TestServerTLSConnectionFactory, get_test_ca_cert_file
2929 from tests.replication._base import BaseMultiWorkerStreamTestCase
30 from tests.server import FakeChannel, FakeTransport
30 from tests.server import FakeChannel, FakeSite, FakeTransport, make_request
3131
3232 logger = logging.getLogger(__name__)
3333
6666 The channel for the *client* request and the *outbound* request for
6767 the media which the caller should respond to.
6868 """
69
70 request, channel = self.make_request(
69 resource = hs.get_media_repository_resource().children[b"download"]
70 _, channel = make_request(
71 self.reactor,
72 FakeSite(resource),
7173 "GET",
7274 "/{}/{}".format(target, media_id),
7375 shorthand=False,
7476 access_token=self.access_token,
75 )
76 request.render(hs.get_media_repository_resource().children[b"download"])
77 await_result=False,
78 )
7779 self.pump()
7880
7981 clients = self.reactor.tcpClients
2121 from synapse.rest.client.v2_alpha import sync
2222
2323 from tests.replication._base import BaseMultiWorkerStreamTestCase
24 from tests.server import make_request
2425 from tests.utils import USE_POSTGRES_FOR_TESTS
2526
2627 logger = logging.getLogger(__name__)
147148 sync_hs = self.make_worker_hs(
148149 "synapse.app.generic_worker", {"worker_name": "sync"},
149150 )
151 sync_hs_site = self._hs_to_site[sync_hs]
150152
151153 # Specially selected room IDs that get persisted on different workers.
152154 room_id1 = "!foo:test"
177179 )
178180
179181 # Do an initial sync so that we're up to date.
180 request, channel = self.make_request("GET", "/sync", access_token=access_token)
181 self.render_on_worker(sync_hs, request)
182 request, channel = make_request(
183 self.reactor, sync_hs_site, "GET", "/sync", access_token=access_token
184 )
182185 next_batch = channel.json_body["next_batch"]
183186
184187 # We now gut wrench into the events stream MultiWriterIdGenerator on
202205
203206 # Check that syncing still gets the new event, despite the gap in the
204207 # stream IDs.
205 request, channel = self.make_request(
206 "GET", "/sync?since={}".format(next_batch), access_token=access_token
207 )
208 self.render_on_worker(sync_hs, request)
208 request, channel = make_request(
209 self.reactor,
210 sync_hs_site,
211 "GET",
212 "/sync?since={}".format(next_batch),
213 access_token=access_token,
214 )
209215
210216 # We should only see the new event and nothing else
211217 self.assertIn(room_id1, channel.json_body["rooms"]["join"])
229235 response = self.helper.send(room_id2, body="Hi!", tok=self.other_access_token)
230236 first_event_in_room2 = response["event_id"]
231237
232 request, channel = self.make_request(
238 request, channel = make_request(
239 self.reactor,
240 sync_hs_site,
233241 "GET",
234242 "/sync?since={}".format(vector_clock_token),
235243 access_token=access_token,
236244 )
237 self.render_on_worker(sync_hs, request)
238245
239246 self.assertNotIn(room_id1, channel.json_body["rooms"]["join"])
240247 self.assertIn(room_id2, channel.json_body["rooms"]["join"])
253260 self.helper.send(room_id1, body="Hi again!", tok=self.other_access_token)
254261 self.helper.send(room_id2, body="Hi again!", tok=self.other_access_token)
255262
256 request, channel = self.make_request(
257 "GET", "/sync?since={}".format(next_batch), access_token=access_token
258 )
259 self.render_on_worker(sync_hs, request)
263 request, channel = make_request(
264 self.reactor,
265 sync_hs_site,
266 "GET",
267 "/sync?since={}".format(next_batch),
268 access_token=access_token,
269 )
260270
261271 prev_batch1 = channel.json_body["rooms"]["join"][room_id1]["timeline"][
262272 "prev_batch"
268278 # Paginating back in the first room should not produce any results, as
269279 # no events have happened in it. This tests that we are correctly
270280 # filtering results based on the vector clock portion.
271 request, channel = self.make_request(
281 request, channel = make_request(
282 self.reactor,
283 sync_hs_site,
272284 "GET",
273285 "/rooms/{}/messages?from={}&to={}&dir=b".format(
274286 room_id1, prev_batch1, vector_clock_token
275287 ),
276288 access_token=access_token,
277289 )
278 self.render_on_worker(sync_hs, request)
279290 self.assertListEqual([], channel.json_body["chunk"])
280291
281292 # Paginating back on the second room should produce the first event
282293 # again. This tests that pagination isn't completely broken.
283 request, channel = self.make_request(
294 request, channel = make_request(
295 self.reactor,
296 sync_hs_site,
284297 "GET",
285298 "/rooms/{}/messages?from={}&to={}&dir=b".format(
286299 room_id2, prev_batch2, vector_clock_token
287300 ),
288301 access_token=access_token,
289302 )
290 self.render_on_worker(sync_hs, request)
291303 self.assertEqual(len(channel.json_body["chunk"]), 1)
292304 self.assertEqual(
293305 channel.json_body["chunk"][0]["event_id"], first_event_in_room2
294306 )
295307
296308 # Paginating forwards should give the same results
297 request, channel = self.make_request(
309 request, channel = make_request(
310 self.reactor,
311 sync_hs_site,
298312 "GET",
299313 "/rooms/{}/messages?from={}&to={}&dir=f".format(
300314 room_id1, vector_clock_token, prev_batch1
301315 ),
302316 access_token=access_token,
303317 )
304 self.render_on_worker(sync_hs, request)
305318 self.assertListEqual([], channel.json_body["chunk"])
306319
307 request, channel = self.make_request(
320 request, channel = make_request(
321 self.reactor,
322 sync_hs_site,
308323 "GET",
309324 "/rooms/{}/messages?from={}&to={}&dir=f".format(
310325 room_id2, vector_clock_token, prev_batch2,
311326 ),
312327 access_token=access_token,
313328 )
314 self.render_on_worker(sync_hs, request)
315329 self.assertEqual(len(channel.json_body["chunk"]), 1)
316330 self.assertEqual(
317331 channel.json_body["chunk"][0]["event_id"], first_event_in_room2
2929 from synapse.rest.client.v2_alpha import groups
3030
3131 from tests import unittest
32 from tests.server import FakeSite, make_request
3233
3334
3435 class VersionTestCase(unittest.HomeserverTestCase):
3536 url = "/_synapse/admin/v1/server_version"
3637
37 def create_test_json_resource(self):
38 def create_test_resource(self):
3839 resource = JsonResource(self.hs)
3940 VersionServlet(self.hs).register(resource)
4041 return resource
4142
4243 def test_version_string(self):
4344 request, channel = self.make_request("GET", self.url, shorthand=False)
44 self.render(request)
4545
4646 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
4747 self.assertEqual(
7474 content={"localpart": "test"},
7575 )
7676
77 self.render(request)
7877 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
7978
8079 group_id = channel.json_body["group_id"]
8786 request, channel = self.make_request(
8887 "PUT", url.encode("ascii"), access_token=self.admin_user_tok, content={}
8988 )
90 self.render(request)
9189 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
9290
9391 url = "/groups/%s/self/accept_invite" % (group_id,)
9492 request, channel = self.make_request(
9593 "PUT", url.encode("ascii"), access_token=self.other_user_token, content={}
9694 )
97 self.render(request)
9895 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
9996
10097 # Check other user knows they're in the group
10299 self.assertIn(group_id, self._get_groups_user_is_in(self.other_user_token))
103100
104101 # Now delete the group
105 url = "/admin/delete_group/" + group_id
102 url = "/_synapse/admin/v1/delete_group/" + group_id
106103 request, channel = self.make_request(
107104 "POST",
108105 url.encode("ascii"),
110107 content={"localpart": "test"},
111108 )
112109
113 self.render(request)
114110 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
115111
116112 # Check group returns 404
130126 "GET", url.encode("ascii"), access_token=self.admin_user_tok
131127 )
132128
133 self.render(request)
134129 self.assertEqual(
135130 expect_code, int(channel.result["code"]), msg=channel.result["body"]
136131 )
142137 "GET", "/joined_groups".encode("ascii"), access_token=access_token
143138 )
144139
145 self.render(request)
146140 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
147141
148142 return channel.json_body["groups"]
221215
222216 def _ensure_quarantined(self, admin_user_tok, server_and_media_id):
223217 """Ensure a piece of media is quarantined when trying to access it."""
224 request, channel = self.make_request(
225 "GET", server_and_media_id, shorthand=False, access_token=admin_user_tok,
226 )
227 request.render(self.download_resource)
228 self.pump(1.0)
218 request, channel = make_request(
219 self.reactor,
220 FakeSite(self.download_resource),
221 "GET",
222 server_and_media_id,
223 shorthand=False,
224 access_token=admin_user_tok,
225 )
229226
230227 # Should be quarantined
231228 self.assertEqual(
246243 request, channel = self.make_request(
247244 "POST", url.encode("ascii"), access_token=non_admin_user_tok,
248245 )
249 self.render(request)
250246
251247 # Expect a forbidden error
252248 self.assertEqual(
260256 request, channel = self.make_request(
261257 "POST", url.encode("ascii"), access_token=non_admin_user_tok,
262258 )
263 self.render(request)
264259
265260 # Expect a forbidden error
266261 self.assertEqual(
286281 server_name, media_id = server_name_and_media_id.split("/")
287282
288283 # Attempt to access the media
289 request, channel = self.make_request(
284 request, channel = make_request(
285 self.reactor,
286 FakeSite(self.download_resource),
290287 "GET",
291288 server_name_and_media_id,
292289 shorthand=False,
293290 access_token=non_admin_user_tok,
294291 )
295 request.render(self.download_resource)
296 self.pump(1.0)
297292
298293 # Should be successful
299294 self.assertEqual(200, int(channel.code), msg=channel.result["body"])
304299 urllib.parse.quote(media_id),
305300 )
306301 request, channel = self.make_request("POST", url, access_token=admin_user_tok,)
307 self.render(request)
308302 self.pump(1.0)
309303 self.assertEqual(200, int(channel.code), msg=channel.result["body"])
310304
357351 room_id
358352 )
359353 request, channel = self.make_request("POST", url, access_token=admin_user_tok,)
360 self.render(request)
361354 self.pump(1.0)
362355 self.assertEqual(200, int(channel.code), msg=channel.result["body"])
363356 self.assertEqual(
404397 request, channel = self.make_request(
405398 "POST", url.encode("ascii"), access_token=admin_user_tok,
406399 )
407 self.render(request)
408400 self.pump(1.0)
409401 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
410402 self.assertEqual(
447439 request, channel = self.make_request(
448440 "POST", url.encode("ascii"), access_token=admin_user_tok,
449441 )
450 self.render(request)
451442 self.pump(1.0)
452443 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
453444 self.assertEqual(
461452 self._ensure_quarantined(admin_user_tok, server_and_media_id_1)
462453
463454 # Attempt to access each piece of media
464 request, channel = self.make_request(
455 request, channel = make_request(
456 self.reactor,
457 FakeSite(self.download_resource),
465458 "GET",
466459 server_and_media_id_2,
467460 shorthand=False,
468461 access_token=non_admin_user_tok,
469462 )
470 request.render(self.download_resource)
471 self.pump(1.0)
472463
473464 # Shouldn't be quarantined
474465 self.assertEqual(
5050 Try to get a device of an user without authentication.
5151 """
5252 request, channel = self.make_request("GET", self.url, b"{}")
53 self.render(request)
5453
5554 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
5655 self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
5756
5857 request, channel = self.make_request("PUT", self.url, b"{}")
59 self.render(request)
6058
6159 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
6260 self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
6361
6462 request, channel = self.make_request("DELETE", self.url, b"{}")
65 self.render(request)
6663
6764 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
6865 self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
7471 request, channel = self.make_request(
7572 "GET", self.url, access_token=self.other_user_token,
7673 )
77 self.render(request)
7874
7975 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
8076 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
8278 request, channel = self.make_request(
8379 "PUT", self.url, access_token=self.other_user_token,
8480 )
85 self.render(request)
8681
8782 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
8883 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
9085 request, channel = self.make_request(
9186 "DELETE", self.url, access_token=self.other_user_token,
9287 )
93 self.render(request)
9488
9589 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
9690 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
107101 request, channel = self.make_request(
108102 "GET", url, access_token=self.admin_user_tok,
109103 )
110 self.render(request)
111104
112105 self.assertEqual(404, channel.code, msg=channel.json_body)
113106 self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
115108 request, channel = self.make_request(
116109 "PUT", url, access_token=self.admin_user_tok,
117110 )
118 self.render(request)
119111
120112 self.assertEqual(404, channel.code, msg=channel.json_body)
121113 self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
123115 request, channel = self.make_request(
124116 "DELETE", url, access_token=self.admin_user_tok,
125117 )
126 self.render(request)
127118
128119 self.assertEqual(404, channel.code, msg=channel.json_body)
129120 self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
140131 request, channel = self.make_request(
141132 "GET", url, access_token=self.admin_user_tok,
142133 )
143 self.render(request)
144134
145135 self.assertEqual(400, channel.code, msg=channel.json_body)
146136 self.assertEqual("Can only lookup local users", channel.json_body["error"])
148138 request, channel = self.make_request(
149139 "PUT", url, access_token=self.admin_user_tok,
150140 )
151 self.render(request)
152141
153142 self.assertEqual(400, channel.code, msg=channel.json_body)
154143 self.assertEqual("Can only lookup local users", channel.json_body["error"])
156145 request, channel = self.make_request(
157146 "DELETE", url, access_token=self.admin_user_tok,
158147 )
159 self.render(request)
160148
161149 self.assertEqual(400, channel.code, msg=channel.json_body)
162150 self.assertEqual("Can only lookup local users", channel.json_body["error"])
172160 request, channel = self.make_request(
173161 "GET", url, access_token=self.admin_user_tok,
174162 )
175 self.render(request)
176163
177164 self.assertEqual(404, channel.code, msg=channel.json_body)
178165 self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
180167 request, channel = self.make_request(
181168 "PUT", url, access_token=self.admin_user_tok,
182169 )
183 self.render(request)
184170
185171 self.assertEqual(200, channel.code, msg=channel.json_body)
186172
187173 request, channel = self.make_request(
188174 "DELETE", url, access_token=self.admin_user_tok,
189175 )
190 self.render(request)
191176
192177 # Delete unknown device returns status 200
193178 self.assertEqual(200, channel.code, msg=channel.json_body)
217202 access_token=self.admin_user_tok,
218203 content=body.encode(encoding="utf_8"),
219204 )
220 self.render(request)
221205
222206 self.assertEqual(400, channel.code, msg=channel.json_body)
223207 self.assertEqual(Codes.TOO_LARGE, channel.json_body["errcode"])
226210 request, channel = self.make_request(
227211 "GET", self.url, access_token=self.admin_user_tok,
228212 )
229 self.render(request)
230213
231214 self.assertEqual(200, channel.code, msg=channel.json_body)
232215 self.assertEqual("new display", channel.json_body["display_name"])
246229 request, channel = self.make_request(
247230 "PUT", self.url, access_token=self.admin_user_tok,
248231 )
249 self.render(request)
250232
251233 self.assertEqual(200, channel.code, msg=channel.json_body)
252234
254236 request, channel = self.make_request(
255237 "GET", self.url, access_token=self.admin_user_tok,
256238 )
257 self.render(request)
258239
259240 self.assertEqual(200, channel.code, msg=channel.json_body)
260241 self.assertEqual("new display", channel.json_body["display_name"])
271252 access_token=self.admin_user_tok,
272253 content=body.encode(encoding="utf_8"),
273254 )
274 self.render(request)
275255
276256 self.assertEqual(200, channel.code, msg=channel.json_body)
277257
279259 request, channel = self.make_request(
280260 "GET", self.url, access_token=self.admin_user_tok,
281261 )
282 self.render(request)
283262
284263 self.assertEqual(200, channel.code, msg=channel.json_body)
285264 self.assertEqual("new displayname", channel.json_body["display_name"])
291270 request, channel = self.make_request(
292271 "GET", self.url, access_token=self.admin_user_tok,
293272 )
294 self.render(request)
295273
296274 self.assertEqual(200, channel.code, msg=channel.json_body)
297275 self.assertEqual(self.other_user, channel.json_body["user_id"])
315293 request, channel = self.make_request(
316294 "DELETE", self.url, access_token=self.admin_user_tok,
317295 )
318 self.render(request)
319296
320297 self.assertEqual(200, channel.code, msg=channel.json_body)
321298
346323 Try to list devices of an user without authentication.
347324 """
348325 request, channel = self.make_request("GET", self.url, b"{}")
349 self.render(request)
350326
351327 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
352328 self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
360336 request, channel = self.make_request(
361337 "GET", self.url, access_token=other_user_token,
362338 )
363 self.render(request)
364339
365340 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
366341 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
373348 request, channel = self.make_request(
374349 "GET", url, access_token=self.admin_user_tok,
375350 )
376 self.render(request)
377351
378352 self.assertEqual(404, channel.code, msg=channel.json_body)
379353 self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
387361 request, channel = self.make_request(
388362 "GET", url, access_token=self.admin_user_tok,
389363 )
390 self.render(request)
391364
392365 self.assertEqual(400, channel.code, msg=channel.json_body)
393366 self.assertEqual("Can only lookup local users", channel.json_body["error"])
402375 request, channel = self.make_request(
403376 "GET", self.url, access_token=self.admin_user_tok,
404377 )
405 self.render(request)
406378
407379 self.assertEqual(200, channel.code, msg=channel.json_body)
408380 self.assertEqual(0, channel.json_body["total"])
421393 request, channel = self.make_request(
422394 "GET", self.url, access_token=self.admin_user_tok,
423395 )
424 self.render(request)
425396
426397 self.assertEqual(200, channel.code, msg=channel.json_body)
427398 self.assertEqual(number_devices, channel.json_body["total"])
460431 Try to delete devices of an user without authentication.
461432 """
462433 request, channel = self.make_request("POST", self.url, b"{}")
463 self.render(request)
464434
465435 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
466436 self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
474444 request, channel = self.make_request(
475445 "POST", self.url, access_token=other_user_token,
476446 )
477 self.render(request)
478447
479448 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
480449 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
487456 request, channel = self.make_request(
488457 "POST", url, access_token=self.admin_user_tok,
489458 )
490 self.render(request)
491459
492460 self.assertEqual(404, channel.code, msg=channel.json_body)
493461 self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
501469 request, channel = self.make_request(
502470 "POST", url, access_token=self.admin_user_tok,
503471 )
504 self.render(request)
505472
506473 self.assertEqual(400, channel.code, msg=channel.json_body)
507474 self.assertEqual("Can only lookup local users", channel.json_body["error"])
517484 access_token=self.admin_user_tok,
518485 content=body.encode(encoding="utf_8"),
519486 )
520 self.render(request)
521487
522488 # Delete unknown devices returns status 200
523489 self.assertEqual(200, channel.code, msg=channel.json_body)
549515 access_token=self.admin_user_tok,
550516 content=body.encode(encoding="utf_8"),
551517 )
552 self.render(request)
553518
554519 self.assertEqual(200, channel.code, msg=channel.json_body)
555520
7474 Try to get an event report without authentication.
7575 """
7676 request, channel = self.make_request("GET", self.url, b"{}")
77 self.render(request)
7877
7978 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
8079 self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
8786 request, channel = self.make_request(
8887 "GET", self.url, access_token=self.other_user_tok,
8988 )
90 self.render(request)
9189
9290 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
9391 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
10098 request, channel = self.make_request(
10199 "GET", self.url, access_token=self.admin_user_tok,
102100 )
103 self.render(request)
104101
105102 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
106103 self.assertEqual(channel.json_body["total"], 20)
116113 request, channel = self.make_request(
117114 "GET", self.url + "?limit=5", access_token=self.admin_user_tok,
118115 )
119 self.render(request)
120116
121117 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
122118 self.assertEqual(channel.json_body["total"], 20)
132128 request, channel = self.make_request(
133129 "GET", self.url + "?from=5", access_token=self.admin_user_tok,
134130 )
135 self.render(request)
136131
137132 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
138133 self.assertEqual(channel.json_body["total"], 20)
148143 request, channel = self.make_request(
149144 "GET", self.url + "?from=5&limit=10", access_token=self.admin_user_tok,
150145 )
151 self.render(request)
152146
153147 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
154148 self.assertEqual(channel.json_body["total"], 20)
166160 self.url + "?room_id=%s" % self.room_id1,
167161 access_token=self.admin_user_tok,
168162 )
169 self.render(request)
170163
171164 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
172165 self.assertEqual(channel.json_body["total"], 10)
187180 self.url + "?user_id=%s" % self.other_user,
188181 access_token=self.admin_user_tok,
189182 )
190 self.render(request)
191183
192184 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
193185 self.assertEqual(channel.json_body["total"], 10)
208200 self.url + "?user_id=%s&room_id=%s" % (self.other_user, self.room_id1),
209201 access_token=self.admin_user_tok,
210202 )
211 self.render(request)
212203
213204 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
214205 self.assertEqual(channel.json_body["total"], 5)
229220 request, channel = self.make_request(
230221 "GET", self.url + "?dir=b", access_token=self.admin_user_tok,
231222 )
232 self.render(request)
233223
234224 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
235225 self.assertEqual(channel.json_body["total"], 20)
246236 request, channel = self.make_request(
247237 "GET", self.url + "?dir=f", access_token=self.admin_user_tok,
248238 )
249 self.render(request)
250239
251240 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
252241 self.assertEqual(channel.json_body["total"], 20)
267256 request, channel = self.make_request(
268257 "GET", self.url + "?dir=bar", access_token=self.admin_user_tok,
269258 )
270 self.render(request)
271259
272260 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
273261 self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
281269 request, channel = self.make_request(
282270 "GET", self.url + "?limit=-5", access_token=self.admin_user_tok,
283271 )
284 self.render(request)
285272
286273 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
287274 self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
294281 request, channel = self.make_request(
295282 "GET", self.url + "?from=-5", access_token=self.admin_user_tok,
296283 )
297 self.render(request)
298284
299285 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
300286 self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
309295 request, channel = self.make_request(
310296 "GET", self.url + "?limit=20", access_token=self.admin_user_tok,
311297 )
312 self.render(request)
313298
314299 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
315300 self.assertEqual(channel.json_body["total"], 20)
321306 request, channel = self.make_request(
322307 "GET", self.url + "?limit=21", access_token=self.admin_user_tok,
323308 )
324 self.render(request)
325309
326310 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
327311 self.assertEqual(channel.json_body["total"], 20)
333317 request, channel = self.make_request(
334318 "GET", self.url + "?limit=19", access_token=self.admin_user_tok,
335319 )
336 self.render(request)
337320
338321 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
339322 self.assertEqual(channel.json_body["total"], 20)
346329 request, channel = self.make_request(
347330 "GET", self.url + "?from=19", access_token=self.admin_user_tok,
348331 )
349 self.render(request)
350332
351333 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
352334 self.assertEqual(channel.json_body["total"], 20)
365347 json.dumps({"score": -100, "reason": "this makes me sad"}),
366348 access_token=user_tok,
367349 )
368 self.render(request)
369350 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
370351
371352 def _check_fields(self, content):
418399 Try to get event report without authentication.
419400 """
420401 request, channel = self.make_request("GET", self.url, b"{}")
421 self.render(request)
422402
423403 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
424404 self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
431411 request, channel = self.make_request(
432412 "GET", self.url, access_token=self.other_user_tok,
433413 )
434 self.render(request)
435414
436415 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
437416 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
444423 request, channel = self.make_request(
445424 "GET", self.url, access_token=self.admin_user_tok,
446425 )
447 self.render(request)
448426
449427 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
450428 self._check_fields(channel.json_body)
460438 "/_synapse/admin/v1/event_reports/-123",
461439 access_token=self.admin_user_tok,
462440 )
463 self.render(request)
464441
465442 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
466443 self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
475452 "/_synapse/admin/v1/event_reports/abcdef",
476453 access_token=self.admin_user_tok,
477454 )
478 self.render(request)
479455
480456 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
481457 self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
490466 "/_synapse/admin/v1/event_reports/",
491467 access_token=self.admin_user_tok,
492468 )
493 self.render(request)
494469
495470 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
496471 self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
509484 "/_synapse/admin/v1/event_reports/123",
510485 access_token=self.admin_user_tok,
511486 )
512 self.render(request)
513487
514488 self.assertEqual(404, int(channel.result["code"]), msg=channel.result["body"])
515489 self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
527501 json.dumps({"score": -100, "reason": "this makes me sad"}),
528502 access_token=user_tok,
529503 )
530 self.render(request)
531504 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
532505
533506 def _check_fields(self, content):
2222 from synapse.rest.media.v1.filepath import MediaFilePaths
2323
2424 from tests import unittest
25 from tests.server import FakeSite, make_request
2526
2627
2728 class DeleteMediaByIDTestCase(unittest.HomeserverTestCase):
4950 url = "/_synapse/admin/v1/media/%s/%s" % (self.server_name, "12345")
5051
5152 request, channel = self.make_request("DELETE", url, b"{}")
52 self.render(request)
5353
5454 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
5555 self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
6666 request, channel = self.make_request(
6767 "DELETE", url, access_token=self.other_user_token,
6868 )
69 self.render(request)
7069
7170 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
7271 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
8079 request, channel = self.make_request(
8180 "DELETE", url, access_token=self.admin_user_tok,
8281 )
83 self.render(request)
8482
8583 self.assertEqual(404, channel.code, msg=channel.json_body)
8684 self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
9492 request, channel = self.make_request(
9593 "DELETE", url, access_token=self.admin_user_tok,
9694 )
97 self.render(request)
9895
9996 self.assertEqual(400, channel.code, msg=channel.json_body)
10097 self.assertEqual("Can only delete local media", channel.json_body["error"])
123120 self.assertEqual(server_name, self.server_name)
124121
125122 # Attempt to access media
126 request, channel = self.make_request(
123 request, channel = make_request(
124 self.reactor,
125 FakeSite(download_resource),
127126 "GET",
128127 server_and_media_id,
129128 shorthand=False,
130129 access_token=self.admin_user_tok,
131130 )
132 request.render(download_resource)
133 self.pump(1.0)
134131
135132 # Should be successful
136133 self.assertEqual(
151148 request, channel = self.make_request(
152149 "DELETE", url, access_token=self.admin_user_tok,
153150 )
154 self.render(request)
155151
156152 self.assertEqual(200, channel.code, msg=channel.json_body)
157153 self.assertEqual(1, channel.json_body["total"])
160156 )
161157
162158 # Attempt to access media
163 request, channel = self.make_request(
159 request, channel = make_request(
160 self.reactor,
161 FakeSite(download_resource),
164162 "GET",
165163 server_and_media_id,
166164 shorthand=False,
167165 access_token=self.admin_user_tok,
168166 )
169 request.render(download_resource)
170 self.pump(1.0)
171167 self.assertEqual(
172168 404,
173169 channel.code,
195191 self.handler = hs.get_device_handler()
196192 self.media_repo = hs.get_media_repository_resource()
197193 self.server_name = hs.hostname
198 self.clock = hs.clock
199194
200195 self.admin_user = self.register_user("admin", "pass", admin=True)
201196 self.admin_user_tok = self.login("admin", "pass")
209204 """
210205
211206 request, channel = self.make_request("POST", self.url, b"{}")
212 self.render(request)
213207
214208 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
215209 self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
224218 request, channel = self.make_request(
225219 "POST", self.url, access_token=self.other_user_token,
226220 )
227 self.render(request)
228221
229222 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
230223 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
238231 request, channel = self.make_request(
239232 "POST", url + "?before_ts=1234", access_token=self.admin_user_tok,
240233 )
241 self.render(request)
242234
243235 self.assertEqual(400, channel.code, msg=channel.json_body)
244236 self.assertEqual("Can only delete local media", channel.json_body["error"])
250242 request, channel = self.make_request(
251243 "POST", self.url, access_token=self.admin_user_tok,
252244 )
253 self.render(request)
254245
255246 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
256247 self.assertEqual(Codes.MISSING_PARAM, channel.json_body["errcode"])
265256 request, channel = self.make_request(
266257 "POST", self.url + "?before_ts=-1234", access_token=self.admin_user_tok,
267258 )
268 self.render(request)
269259
270260 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
271261 self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
279269 self.url + "?before_ts=1234&size_gt=-1234",
280270 access_token=self.admin_user_tok,
281271 )
282 self.render(request)
283272
284273 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
285274 self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
293282 self.url + "?before_ts=1234&keep_profiles=not_bool",
294283 access_token=self.admin_user_tok,
295284 )
296 self.render(request)
297285
298286 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
299287 self.assertEqual(Codes.UNKNOWN, channel.json_body["errcode"])
324312 self.url + "?before_ts=" + str(now_ms),
325313 access_token=self.admin_user_tok,
326314 )
327 self.render(request)
328315 self.assertEqual(200, channel.code, msg=channel.json_body)
329316 self.assertEqual(1, channel.json_body["total"])
330317 self.assertEqual(
349336 self.url + "?before_ts=" + str(now_ms),
350337 access_token=self.admin_user_tok,
351338 )
352 self.render(request)
353339 self.assertEqual(200, channel.code, msg=channel.json_body)
354340 self.assertEqual(0, channel.json_body["total"])
355341
362348 self.url + "?before_ts=" + str(now_ms),
363349 access_token=self.admin_user_tok,
364350 )
365 self.render(request)
366351 self.assertEqual(200, channel.code, msg=channel.json_body)
367352 self.assertEqual(1, channel.json_body["total"])
368353 self.assertEqual(
386371 self.url + "?before_ts=" + str(now_ms) + "&size_gt=67",
387372 access_token=self.admin_user_tok,
388373 )
389 self.render(request)
390374 self.assertEqual(200, channel.code, msg=channel.json_body)
391375 self.assertEqual(0, channel.json_body["total"])
392376
398382 self.url + "?before_ts=" + str(now_ms) + "&size_gt=66",
399383 access_token=self.admin_user_tok,
400384 )
401 self.render(request)
402385 self.assertEqual(200, channel.code, msg=channel.json_body)
403386 self.assertEqual(1, channel.json_body["total"])
404387 self.assertEqual(
423406 content=json.dumps({"avatar_url": "mxc://%s" % (server_and_media_id,)}),
424407 access_token=self.admin_user_tok,
425408 )
426 self.render(request)
427409 self.assertEqual(200, channel.code, msg=channel.json_body)
428410
429411 now_ms = self.clock.time_msec()
432414 self.url + "?before_ts=" + str(now_ms) + "&keep_profiles=true",
433415 access_token=self.admin_user_tok,
434416 )
435 self.render(request)
436417 self.assertEqual(200, channel.code, msg=channel.json_body)
437418 self.assertEqual(0, channel.json_body["total"])
438419
444425 self.url + "?before_ts=" + str(now_ms) + "&keep_profiles=false",
445426 access_token=self.admin_user_tok,
446427 )
447 self.render(request)
448428 self.assertEqual(200, channel.code, msg=channel.json_body)
449429 self.assertEqual(1, channel.json_body["total"])
450430 self.assertEqual(
470450 content=json.dumps({"url": "mxc://%s" % (server_and_media_id,)}),
471451 access_token=self.admin_user_tok,
472452 )
473 self.render(request)
474453 self.assertEqual(200, channel.code, msg=channel.json_body)
475454
476455 now_ms = self.clock.time_msec()
479458 self.url + "?before_ts=" + str(now_ms) + "&keep_profiles=true",
480459 access_token=self.admin_user_tok,
481460 )
482 self.render(request)
483461 self.assertEqual(200, channel.code, msg=channel.json_body)
484462 self.assertEqual(0, channel.json_body["total"])
485463
491469 self.url + "?before_ts=" + str(now_ms) + "&keep_profiles=false",
492470 access_token=self.admin_user_tok,
493471 )
494 self.render(request)
495472 self.assertEqual(200, channel.code, msg=channel.json_body)
496473 self.assertEqual(1, channel.json_body["total"])
497474 self.assertEqual(
534511 media_id = server_and_media_id.split("/")[1]
535512 local_path = self.filepaths.local_media_filepath(media_id)
536513
537 request, channel = self.make_request(
514 request, channel = make_request(
515 self.reactor,
516 FakeSite(download_resource),
538517 "GET",
539518 server_and_media_id,
540519 shorthand=False,
541520 access_token=self.admin_user_tok,
542521 )
543 request.render(download_resource)
544 self.pump(1.0)
545522
546523 if expect_success:
547524 self.assertEqual(
7777 )
7878
7979 # Test that the admin can still send shutdown
80 url = "admin/shutdown_room/" + room_id
80 url = "/_synapse/admin/v1/shutdown_room/" + room_id
8181 request, channel = self.make_request(
8282 "POST",
8383 url.encode("ascii"),
8484 json.dumps({"new_room_user_id": self.admin_user}),
8585 access_token=self.admin_user_tok,
8686 )
87 self.render(request)
8887
8988 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
9089
109108 json.dumps({"history_visibility": "world_readable"}),
110109 access_token=self.other_user_token,
111110 )
112 self.render(request)
113111 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
114112
115113 # Test that the admin can still send shutdown
116 url = "admin/shutdown_room/" + room_id
114 url = "/_synapse/admin/v1/shutdown_room/" + room_id
117115 request, channel = self.make_request(
118116 "POST",
119117 url.encode("ascii"),
120118 json.dumps({"new_room_user_id": self.admin_user}),
121119 access_token=self.admin_user_tok,
122120 )
123 self.render(request)
124121
125122 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
126123
135132 request, channel = self.make_request(
136133 "GET", url.encode("ascii"), access_token=self.admin_user_tok
137134 )
138 self.render(request)
139135 self.assertEqual(
140136 expect_code, int(channel.result["code"]), msg=channel.result["body"]
141137 )
144140 request, channel = self.make_request(
145141 "GET", url.encode("ascii"), access_token=self.admin_user_tok
146142 )
147 self.render(request)
148143 self.assertEqual(
149144 expect_code, int(channel.result["code"]), msg=channel.result["body"]
150145 )
191186 request, channel = self.make_request(
192187 "POST", self.url, json.dumps({}), access_token=self.other_user_tok,
193188 )
194 self.render(request)
195189
196190 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
197191 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
205199 request, channel = self.make_request(
206200 "POST", url, json.dumps({}), access_token=self.admin_user_tok,
207201 )
208 self.render(request)
209202
210203 self.assertEqual(404, int(channel.result["code"]), msg=channel.result["body"])
211204 self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
219212 request, channel = self.make_request(
220213 "POST", url, json.dumps({}), access_token=self.admin_user_tok,
221214 )
222 self.render(request)
223215
224216 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
225217 self.assertEqual(
238230 content=body.encode(encoding="utf_8"),
239231 access_token=self.admin_user_tok,
240232 )
241 self.render(request)
242233
243234 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
244235 self.assertIn("new_room_id", channel.json_body)
258249 content=body.encode(encoding="utf_8"),
259250 access_token=self.admin_user_tok,
260251 )
261 self.render(request)
262252
263253 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
264254 self.assertEqual(
277267 content=body.encode(encoding="utf_8"),
278268 access_token=self.admin_user_tok,
279269 )
280 self.render(request)
281270
282271 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
283272 self.assertEqual(Codes.BAD_JSON, channel.json_body["errcode"])
294283 content=body.encode(encoding="utf_8"),
295284 access_token=self.admin_user_tok,
296285 )
297 self.render(request)
298286
299287 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
300288 self.assertEqual(Codes.BAD_JSON, channel.json_body["errcode"])
321309 content=body.encode(encoding="utf_8"),
322310 access_token=self.admin_user_tok,
323311 )
324 self.render(request)
325312
326313 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
327314 self.assertEqual(None, channel.json_body["new_room_id"])
355342 content=body.encode(encoding="utf_8"),
356343 access_token=self.admin_user_tok,
357344 )
358 self.render(request)
359345
360346 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
361347 self.assertEqual(None, channel.json_body["new_room_id"])
390376 content=body.encode(encoding="utf_8"),
391377 access_token=self.admin_user_tok,
392378 )
393 self.render(request)
394379
395380 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
396381 self.assertEqual(None, channel.json_body["new_room_id"])
438423 json.dumps({"new_room_user_id": self.admin_user}),
439424 access_token=self.admin_user_tok,
440425 )
441 self.render(request)
442426
443427 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
444428 self.assertEqual(self.other_user, channel.json_body["kicked_users"][0])
469453 json.dumps({"history_visibility": "world_readable"}),
470454 access_token=self.other_user_tok,
471455 )
472 self.render(request)
473456 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
474457
475458 # Test that room is not purged
487470 json.dumps({"new_room_user_id": self.admin_user}),
488471 access_token=self.admin_user_tok,
489472 )
490 self.render(request)
491473
492474 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
493475 self.assertEqual(self.other_user, channel.json_body["kicked_users"][0])
550532 request, channel = self.make_request(
551533 "GET", url.encode("ascii"), access_token=self.admin_user_tok
552534 )
553 self.render(request)
554535 self.assertEqual(
555536 expect_code, int(channel.result["code"]), msg=channel.result["body"]
556537 )
559540 request, channel = self.make_request(
560541 "GET", url.encode("ascii"), access_token=self.admin_user_tok
561542 )
562 self.render(request)
563543 self.assertEqual(
564544 expect_code, int(channel.result["code"]), msg=channel.result["body"]
565545 )
594574 {"room_id": room_id},
595575 access_token=self.admin_user_tok,
596576 )
597 self.render(request)
598577
599578 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
600579
646625 request, channel = self.make_request(
647626 "GET", url.encode("ascii"), access_token=self.admin_user_tok,
648627 )
649 self.render(request)
650628
651629 # Check request completed successfully
652630 self.assertEqual(200, int(channel.code), msg=channel.json_body)
728706 request, channel = self.make_request(
729707 "GET", url.encode("ascii"), access_token=self.admin_user_tok,
730708 )
731 self.render(request)
732709 self.assertEqual(
733710 200, int(channel.result["code"]), msg=channel.result["body"]
734711 )
769746 request, channel = self.make_request(
770747 "GET", url.encode("ascii"), access_token=self.admin_user_tok,
771748 )
772 self.render(request)
773749 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
774750
775751 def test_correct_room_attributes(self):
793769 {"room_id": room_id},
794770 access_token=self.admin_user_tok,
795771 )
796 self.render(request)
797772 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
798773
799774 # Set this new alias as the canonical alias for this room
821796 request, channel = self.make_request(
822797 "GET", url.encode("ascii"), access_token=self.admin_user_tok,
823798 )
824 self.render(request)
825799 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
826800
827801 # Check that rooms were returned
866840 {"room_id": room_id},
867841 access_token=admin_user_tok,
868842 )
869 self.render(request)
870843 self.assertEqual(
871844 200, int(channel.result["code"]), msg=channel.result["body"]
872845 )
904877 request, channel = self.make_request(
905878 "GET", url.encode("ascii"), access_token=self.admin_user_tok,
906879 )
907 self.render(request)
908880 self.assertEqual(200, channel.code, msg=channel.json_body)
909881
910882 # Check that rooms were returned
10411013 request, channel = self.make_request(
10421014 "GET", url.encode("ascii"), access_token=self.admin_user_tok,
10431015 )
1044 self.render(request)
10451016 self.assertEqual(expected_http_code, channel.code, msg=channel.json_body)
10461017
10471018 if expected_http_code != 200:
11031074 request, channel = self.make_request(
11041075 "GET", url.encode("ascii"), access_token=self.admin_user_tok,
11051076 )
1106 self.render(request)
11071077 self.assertEqual(200, channel.code, msg=channel.json_body)
11081078
11091079 self.assertIn("room_id", channel.json_body)
11511121 request, channel = self.make_request(
11521122 "GET", url.encode("ascii"), access_token=self.admin_user_tok,
11531123 )
1154 self.render(request)
11551124 self.assertEqual(200, channel.code, msg=channel.json_body)
11561125
11571126 self.assertCountEqual(
11631132 request, channel = self.make_request(
11641133 "GET", url.encode("ascii"), access_token=self.admin_user_tok,
11651134 )
1166 self.render(request)
11671135 self.assertEqual(200, channel.code, msg=channel.json_body)
11681136
11691137 self.assertCountEqual(
12071175 content=body.encode(encoding="utf_8"),
12081176 access_token=self.second_tok,
12091177 )
1210 self.render(request)
12111178
12121179 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
12131180 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
12241191 content=body.encode(encoding="utf_8"),
12251192 access_token=self.admin_user_tok,
12261193 )
1227 self.render(request)
12281194
12291195 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
12301196 self.assertEqual(Codes.MISSING_PARAM, channel.json_body["errcode"])
12411207 content=body.encode(encoding="utf_8"),
12421208 access_token=self.admin_user_tok,
12431209 )
1244 self.render(request)
12451210
12461211 self.assertEqual(404, int(channel.result["code"]), msg=channel.result["body"])
12471212 self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
12581223 content=body.encode(encoding="utf_8"),
12591224 access_token=self.admin_user_tok,
12601225 )
1261 self.render(request)
12621226
12631227 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
12641228 self.assertEqual(
12791243 content=body.encode(encoding="utf_8"),
12801244 access_token=self.admin_user_tok,
12811245 )
1282 self.render(request)
12831246
12841247 self.assertEqual(404, int(channel.result["code"]), msg=channel.result["body"])
12851248 self.assertEqual("No known servers", channel.json_body["error"])
12971260 content=body.encode(encoding="utf_8"),
12981261 access_token=self.admin_user_tok,
12991262 )
1300 self.render(request)
13011263
13021264 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
13031265 self.assertEqual(
13171279 content=body.encode(encoding="utf_8"),
13181280 access_token=self.admin_user_tok,
13191281 )
1320 self.render(request)
13211282
13221283 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
13231284 self.assertEqual(self.public_room_id, channel.json_body["room_id"])
13271288 request, channel = self.make_request(
13281289 "GET", "/_matrix/client/r0/joined_rooms", access_token=self.second_tok,
13291290 )
1330 self.render(request)
13311291 self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
13321292 self.assertEqual(self.public_room_id, channel.json_body["joined_rooms"][0])
13331293
13481308 content=body.encode(encoding="utf_8"),
13491309 access_token=self.admin_user_tok,
13501310 )
1351 self.render(request)
13521311
13531312 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
13541313 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
13761335 request, channel = self.make_request(
13771336 "GET", "/_matrix/client/r0/joined_rooms", access_token=self.admin_user_tok,
13781337 )
1379 self.render(request)
13801338 self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
13811339 self.assertEqual(private_room_id, channel.json_body["joined_rooms"][0])
13821340
13911349 content=body.encode(encoding="utf_8"),
13921350 access_token=self.admin_user_tok,
13931351 )
1394 self.render(request)
13951352 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
13961353 self.assertEqual(private_room_id, channel.json_body["room_id"])
13971354
14001357 request, channel = self.make_request(
14011358 "GET", "/_matrix/client/r0/joined_rooms", access_token=self.second_tok,
14021359 )
1403 self.render(request)
14041360 self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
14051361 self.assertEqual(private_room_id, channel.json_body["joined_rooms"][0])
14061362
14211377 content=body.encode(encoding="utf_8"),
14221378 access_token=self.admin_user_tok,
14231379 )
1424 self.render(request)
14251380
14261381 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
14271382 self.assertEqual(private_room_id, channel.json_body["room_id"])
14311386 request, channel = self.make_request(
14321387 "GET", "/_matrix/client/r0/joined_rooms", access_token=self.second_tok,
14331388 )
1434 self.render(request)
14351389 self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
14361390 self.assertEqual(private_room_id, channel.json_body["joined_rooms"][0])
14371391
4646 Try to list users without authentication.
4747 """
4848 request, channel = self.make_request("GET", self.url, b"{}")
49 self.render(request)
5049
5150 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
5251 self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
5857 request, channel = self.make_request(
5958 "GET", self.url, json.dumps({}), access_token=self.other_user_tok,
6059 )
61 self.render(request)
6260
6361 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
6462 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
7169 request, channel = self.make_request(
7270 "GET", self.url + "?order_by=bar", access_token=self.admin_user_tok,
7371 )
74 self.render(request)
7572
7673 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
7774 self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
8077 request, channel = self.make_request(
8178 "GET", self.url + "?from=-5", access_token=self.admin_user_tok,
8279 )
83 self.render(request)
8480
8581 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
8682 self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
8985 request, channel = self.make_request(
9086 "GET", self.url + "?limit=-5", access_token=self.admin_user_tok,
9187 )
92 self.render(request)
9388
9489 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
9590 self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
9893 request, channel = self.make_request(
9994 "GET", self.url + "?from_ts=-1234", access_token=self.admin_user_tok,
10095 )
101 self.render(request)
10296
10397 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
10498 self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
107101 request, channel = self.make_request(
108102 "GET", self.url + "?until_ts=-1234", access_token=self.admin_user_tok,
109103 )
110 self.render(request)
111104
112105 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
113106 self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
118111 self.url + "?from_ts=10&until_ts=5",
119112 access_token=self.admin_user_tok,
120113 )
121 self.render(request)
122114
123115 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
124116 self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
127119 request, channel = self.make_request(
128120 "GET", self.url + "?search_term=", access_token=self.admin_user_tok,
129121 )
130 self.render(request)
131122
132123 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
133124 self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
136127 request, channel = self.make_request(
137128 "GET", self.url + "?dir=bar", access_token=self.admin_user_tok,
138129 )
139 self.render(request)
140130
141131 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
142132 self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
150140 request, channel = self.make_request(
151141 "GET", self.url + "?limit=5", access_token=self.admin_user_tok,
152142 )
153 self.render(request)
154143
155144 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
156145 self.assertEqual(channel.json_body["total"], 10)
167156 request, channel = self.make_request(
168157 "GET", self.url + "?from=5", access_token=self.admin_user_tok,
169158 )
170 self.render(request)
171159
172160 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
173161 self.assertEqual(channel.json_body["total"], 20)
184172 request, channel = self.make_request(
185173 "GET", self.url + "?from=5&limit=10", access_token=self.admin_user_tok,
186174 )
187 self.render(request)
188175
189176 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
190177 self.assertEqual(channel.json_body["total"], 20)
205192 request, channel = self.make_request(
206193 "GET", self.url + "?limit=20", access_token=self.admin_user_tok,
207194 )
208 self.render(request)
209195
210196 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
211197 self.assertEqual(channel.json_body["total"], number_users)
217203 request, channel = self.make_request(
218204 "GET", self.url + "?limit=21", access_token=self.admin_user_tok,
219205 )
220 self.render(request)
221206
222207 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
223208 self.assertEqual(channel.json_body["total"], number_users)
229214 request, channel = self.make_request(
230215 "GET", self.url + "?limit=19", access_token=self.admin_user_tok,
231216 )
232 self.render(request)
233217
234218 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
235219 self.assertEqual(channel.json_body["total"], number_users)
241225 request, channel = self.make_request(
242226 "GET", self.url + "?from=19", access_token=self.admin_user_tok,
243227 )
244 self.render(request)
245228
246229 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
247230 self.assertEqual(channel.json_body["total"], number_users)
257240 request, channel = self.make_request(
258241 "GET", self.url, access_token=self.admin_user_tok,
259242 )
260 self.render(request)
261243
262244 self.assertEqual(200, channel.code, msg=channel.json_body)
263245 self.assertEqual(0, channel.json_body["total"])
336318 request, channel = self.make_request(
337319 "GET", self.url, access_token=self.admin_user_tok,
338320 )
339 self.render(request)
340321 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
341322 self.assertEqual(channel.json_body["users"][0]["media_count"], 3)
342323
345326 request, channel = self.make_request(
346327 "GET", self.url + "?from_ts=%s" % (ts1,), access_token=self.admin_user_tok,
347328 )
348 self.render(request)
349329 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
350330 self.assertEqual(channel.json_body["total"], 0)
351331
361341 self.url + "?from_ts=%s&until_ts=%s" % (ts1, ts2),
362342 access_token=self.admin_user_tok,
363343 )
364 self.render(request)
365344 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
366345 self.assertEqual(channel.json_body["users"][0]["media_count"], 3)
367346
369348 request, channel = self.make_request(
370349 "GET", self.url + "?until_ts=%s" % (ts2,), access_token=self.admin_user_tok,
371350 )
372 self.render(request)
373351 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
374352 self.assertEqual(channel.json_body["users"][0]["media_count"], 6)
375353
380358 request, channel = self.make_request(
381359 "GET", self.url, access_token=self.admin_user_tok,
382360 )
383 self.render(request)
384361 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
385362 self.assertEqual(channel.json_body["total"], 20)
386363
390367 self.url + "?search_term=foo_user_1",
391368 access_token=self.admin_user_tok,
392369 )
393 self.render(request)
394370 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
395371 self.assertEqual(channel.json_body["total"], 11)
396372
400376 self.url + "?search_term=bar_user_10",
401377 access_token=self.admin_user_tok,
402378 )
403 self.render(request)
404379 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
405380 self.assertEqual(channel.json_body["users"][0]["displayname"], "bar_user_10")
406381 self.assertEqual(channel.json_body["total"], 1)
409384 request, channel = self.make_request(
410385 "GET", self.url + "?search_term=foobar", access_token=self.admin_user_tok,
411386 )
412 self.render(request)
413387 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
414388 self.assertEqual(channel.json_body["total"], 0)
415389
475449 request, channel = self.make_request(
476450 "GET", url.encode("ascii"), access_token=self.admin_user_tok,
477451 )
478 self.render(request)
479452 self.assertEqual(200, channel.code, msg=channel.json_body)
480453 self.assertEqual(channel.json_body["total"], len(expected_user_list))
481454
2323 import synapse.rest.admin
2424 from synapse.api.constants import UserTypes
2525 from synapse.api.errors import Codes, HttpResponseException, ResourceLimitError
26 from synapse.rest.client.v1 import login, profile, room
27 from synapse.rest.client.v2_alpha import sync
26 from synapse.rest.client.v1 import login, logout, profile, room
27 from synapse.rest.client.v2_alpha import devices, sync
2828
2929 from tests import unittest
3030 from tests.test_utils import make_awaitable
4040
4141 def make_homeserver(self, reactor, clock):
4242
43 self.url = "/_matrix/client/r0/admin/register"
43 self.url = "/_synapse/admin/v1/register"
4444
4545 self.registration_handler = Mock()
4646 self.identity_handler = Mock()
7070 self.hs.config.registration_shared_secret = None
7171
7272 request, channel = self.make_request("POST", self.url, b"{}")
73 self.render(request)
7473
7574 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
7675 self.assertEqual(
8887 self.hs.get_secrets = Mock(return_value=secrets)
8988
9089 request, channel = self.make_request("GET", self.url)
91 self.render(request)
9290
9391 self.assertEqual(channel.json_body, {"nonce": "abcd"})
9492
9896 only last for SALT_TIMEOUT (60s).
9997 """
10098 request, channel = self.make_request("GET", self.url)
101 self.render(request)
10299 nonce = channel.json_body["nonce"]
103100
104101 # 59 seconds
106103
107104 body = json.dumps({"nonce": nonce})
108105 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
109 self.render(request)
110106
111107 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
112108 self.assertEqual("username must be specified", channel.json_body["error"])
115111 self.reactor.advance(2)
116112
117113 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
118 self.render(request)
119114
120115 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
121116 self.assertEqual("unrecognised nonce", channel.json_body["error"])
125120 Only the provided nonce can be used, as it's checked in the MAC.
126121 """
127122 request, channel = self.make_request("GET", self.url)
128 self.render(request)
129123 nonce = channel.json_body["nonce"]
130124
131125 want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1)
142136 }
143137 )
144138 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
145 self.render(request)
146139
147140 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
148141 self.assertEqual("HMAC incorrect", channel.json_body["error"])
153146 user is registered.
154147 """
155148 request, channel = self.make_request("GET", self.url)
156 self.render(request)
157149 nonce = channel.json_body["nonce"]
158150
159151 want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1)
173165 }
174166 )
175167 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
176 self.render(request)
177168
178169 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
179170 self.assertEqual("@bob:test", channel.json_body["user_id"])
183174 A valid unrecognised nonce.
184175 """
185176 request, channel = self.make_request("GET", self.url)
186 self.render(request)
187177 nonce = channel.json_body["nonce"]
188178
189179 want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1)
200190 }
201191 )
202192 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
203 self.render(request)
204193
205194 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
206195 self.assertEqual("@bob:test", channel.json_body["user_id"])
207196
208197 # Now, try and reuse it
209198 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
210 self.render(request)
211199
212200 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
213201 self.assertEqual("unrecognised nonce", channel.json_body["error"])
221209
222210 def nonce():
223211 request, channel = self.make_request("GET", self.url)
224 self.render(request)
225212 return channel.json_body["nonce"]
226213
227214 #
231218 # Must be present
232219 body = json.dumps({})
233220 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
234 self.render(request)
235221
236222 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
237223 self.assertEqual("nonce must be specified", channel.json_body["error"])
243229 # Must be present
244230 body = json.dumps({"nonce": nonce()})
245231 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
246 self.render(request)
247232
248233 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
249234 self.assertEqual("username must be specified", channel.json_body["error"])
251236 # Must be a string
252237 body = json.dumps({"nonce": nonce(), "username": 1234})
253238 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
254 self.render(request)
255239
256240 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
257241 self.assertEqual("Invalid username", channel.json_body["error"])
259243 # Must not have null bytes
260244 body = json.dumps({"nonce": nonce(), "username": "abcd\u0000"})
261245 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
262 self.render(request)
263246
264247 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
265248 self.assertEqual("Invalid username", channel.json_body["error"])
267250 # Must not have null bytes
268251 body = json.dumps({"nonce": nonce(), "username": "a" * 1000})
269252 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
270 self.render(request)
271253
272254 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
273255 self.assertEqual("Invalid username", channel.json_body["error"])
279261 # Must be present
280262 body = json.dumps({"nonce": nonce(), "username": "a"})
281263 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
282 self.render(request)
283264
284265 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
285266 self.assertEqual("password must be specified", channel.json_body["error"])
287268 # Must be a string
288269 body = json.dumps({"nonce": nonce(), "username": "a", "password": 1234})
289270 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
290 self.render(request)
291271
292272 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
293273 self.assertEqual("Invalid password", channel.json_body["error"])
295275 # Must not have null bytes
296276 body = json.dumps({"nonce": nonce(), "username": "a", "password": "abcd\u0000"})
297277 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
298 self.render(request)
299278
300279 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
301280 self.assertEqual("Invalid password", channel.json_body["error"])
303282 # Super long
304283 body = json.dumps({"nonce": nonce(), "username": "a", "password": "A" * 1000})
305284 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
306 self.render(request)
307285
308286 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
309287 self.assertEqual("Invalid password", channel.json_body["error"])
322300 }
323301 )
324302 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
325 self.render(request)
326303
327304 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
328305 self.assertEqual("Invalid user type", channel.json_body["error"])
334311
335312 # set no displayname
336313 request, channel = self.make_request("GET", self.url)
337 self.render(request)
338314 nonce = channel.json_body["nonce"]
339315
340316 want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1)
345321 {"nonce": nonce, "username": "bob1", "password": "abc123", "mac": want_mac}
346322 )
347323 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
348 self.render(request)
349324
350325 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
351326 self.assertEqual("@bob1:test", channel.json_body["user_id"])
352327
353328 request, channel = self.make_request("GET", "/profile/@bob1:test/displayname")
354 self.render(request)
355329 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
356330 self.assertEqual("bob1", channel.json_body["displayname"])
357331
358332 # displayname is None
359333 request, channel = self.make_request("GET", self.url)
360 self.render(request)
361334 nonce = channel.json_body["nonce"]
362335
363336 want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1)
374347 }
375348 )
376349 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
377 self.render(request)
378350
379351 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
380352 self.assertEqual("@bob2:test", channel.json_body["user_id"])
381353
382354 request, channel = self.make_request("GET", "/profile/@bob2:test/displayname")
383 self.render(request)
384355 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
385356 self.assertEqual("bob2", channel.json_body["displayname"])
386357
387358 # displayname is empty
388359 request, channel = self.make_request("GET", self.url)
389 self.render(request)
390360 nonce = channel.json_body["nonce"]
391361
392362 want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1)
403373 }
404374 )
405375 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
406 self.render(request)
407376
408377 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
409378 self.assertEqual("@bob3:test", channel.json_body["user_id"])
410379
411380 request, channel = self.make_request("GET", "/profile/@bob3:test/displayname")
412 self.render(request)
413381 self.assertEqual(404, int(channel.result["code"]), msg=channel.result["body"])
414382
415383 # set displayname
416384 request, channel = self.make_request("GET", self.url)
417 self.render(request)
418385 nonce = channel.json_body["nonce"]
419386
420387 want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1)
431398 }
432399 )
433400 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
434 self.render(request)
435401
436402 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
437403 self.assertEqual("@bob4:test", channel.json_body["user_id"])
438404
439405 request, channel = self.make_request("GET", "/profile/@bob4:test/displayname")
440 self.render(request)
441406 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
442407 self.assertEqual("Bob's Name", channel.json_body["displayname"])
443408
464429
465430 # Register new user with admin API
466431 request, channel = self.make_request("GET", self.url)
467 self.render(request)
468432 nonce = channel.json_body["nonce"]
469433
470434 want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1)
484448 }
485449 )
486450 request, channel = self.make_request("POST", self.url, body.encode("utf8"))
487 self.render(request)
488451
489452 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
490453 self.assertEqual("@bob:test", channel.json_body["user_id"])
510473 Try to list users without authentication.
511474 """
512475 request, channel = self.make_request("GET", self.url, b"{}")
513 self.render(request)
514476
515477 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
516478 self.assertEqual("M_MISSING_TOKEN", channel.json_body["errcode"])
525487 b"{}",
526488 access_token=self.admin_user_tok,
527489 )
528 self.render(request)
529490
530491 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
531492 self.assertEqual(3, len(channel.json_body["users"]))
561522 request, channel = self.make_request(
562523 "GET", url, access_token=self.other_user_token,
563524 )
564 self.render(request)
565525
566526 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
567527 self.assertEqual("You are not a server admin", channel.json_body["error"])
569529 request, channel = self.make_request(
570530 "PUT", url, access_token=self.other_user_token, content=b"{}",
571531 )
572 self.render(request)
573532
574533 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
575534 self.assertEqual("You are not a server admin", channel.json_body["error"])
584543 "/_synapse/admin/v2/users/@unknown_person:test",
585544 access_token=self.admin_user_tok,
586545 )
587 self.render(request)
588546
589547 self.assertEqual(404, channel.code, msg=channel.json_body)
590548 self.assertEqual("M_NOT_FOUND", channel.json_body["errcode"])
612570 access_token=self.admin_user_tok,
613571 content=body.encode(encoding="utf_8"),
614572 )
615 self.render(request)
616573
617574 self.assertEqual(201, int(channel.result["code"]), msg=channel.result["body"])
618575 self.assertEqual("@bob:test", channel.json_body["name"])
625582 request, channel = self.make_request(
626583 "GET", url, access_token=self.admin_user_tok,
627584 )
628 self.render(request)
629585
630586 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
631587 self.assertEqual("@bob:test", channel.json_body["name"])
658614 access_token=self.admin_user_tok,
659615 content=body.encode(encoding="utf_8"),
660616 )
661 self.render(request)
662617
663618 self.assertEqual(201, int(channel.result["code"]), msg=channel.result["body"])
664619 self.assertEqual("@bob:test", channel.json_body["name"])
671626 request, channel = self.make_request(
672627 "GET", url, access_token=self.admin_user_tok,
673628 )
674 self.render(request)
675629
676630 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
677631 self.assertEqual("@bob:test", channel.json_body["name"])
699653 request, channel = self.make_request(
700654 "GET", "/sync", access_token=self.admin_user_tok
701655 )
702 self.render(request)
703656
704657 if channel.code != 200:
705658 raise HttpResponseException(
728681 access_token=self.admin_user_tok,
729682 content=body.encode(encoding="utf_8"),
730683 )
731 self.render(request)
732684
733685 self.assertEqual(201, int(channel.result["code"]), msg=channel.result["body"])
734686 self.assertEqual("@bob:test", channel.json_body["name"])
768720 access_token=self.admin_user_tok,
769721 content=body.encode(encoding="utf_8"),
770722 )
771 self.render(request)
772723
773724 # Admin user is not blocked by mau anymore
774725 self.assertEqual(201, int(channel.result["code"]), msg=channel.result["body"])
806757 access_token=self.admin_user_tok,
807758 content=body.encode(encoding="utf_8"),
808759 )
809 self.render(request)
810760
811761 self.assertEqual(201, int(channel.result["code"]), msg=channel.result["body"])
812762 self.assertEqual("@bob:test", channel.json_body["name"])
851801 access_token=self.admin_user_tok,
852802 content=body.encode(encoding="utf_8"),
853803 )
854 self.render(request)
855804
856805 self.assertEqual(201, int(channel.result["code"]), msg=channel.result["body"])
857806 self.assertEqual("@bob:test", channel.json_body["name"])
878827 access_token=self.admin_user_tok,
879828 content=body.encode(encoding="utf_8"),
880829 )
881 self.render(request)
882830
883831 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
884832
896844 access_token=self.admin_user_tok,
897845 content=body.encode(encoding="utf_8"),
898846 )
899 self.render(request)
900847
901848 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
902849 self.assertEqual("@user:test", channel.json_body["name"])
906853 request, channel = self.make_request(
907854 "GET", self.url_other_user, access_token=self.admin_user_tok,
908855 )
909 self.render(request)
910856
911857 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
912858 self.assertEqual("@user:test", channel.json_body["name"])
928874 access_token=self.admin_user_tok,
929875 content=body.encode(encoding="utf_8"),
930876 )
931 self.render(request)
932877
933878 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
934879 self.assertEqual("@user:test", channel.json_body["name"])
939884 request, channel = self.make_request(
940885 "GET", self.url_other_user, access_token=self.admin_user_tok,
941886 )
942 self.render(request)
943887
944888 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
945889 self.assertEqual("@user:test", channel.json_body["name"])
960904 access_token=self.admin_user_tok,
961905 content=body.encode(encoding="utf_8"),
962906 )
963 self.render(request)
964907
965908 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
966909 self.assertEqual("@user:test", channel.json_body["name"])
971914 request, channel = self.make_request(
972915 "GET", self.url_other_user, access_token=self.admin_user_tok,
973916 )
974 self.render(request)
975917
976918 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
977919 self.assertEqual("@user:test", channel.json_body["name"])
989931 access_token=self.admin_user_tok,
990932 content=json.dumps({"deactivated": True}).encode(encoding="utf_8"),
991933 )
992 self.render(request)
993934 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
994935 self._is_erased("@user:test", False)
995936 d = self.store.mark_user_erased("@user:test")
1003944 access_token=self.admin_user_tok,
1004945 content=json.dumps({"deactivated": False}).encode(encoding="utf_8"),
1005946 )
1006 self.render(request)
1007947 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
1008948
1009949 # Reactivate the user.
1015955 encoding="utf_8"
1016956 ),
1017957 )
1018 self.render(request)
1019958 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
1020959
1021960 # Get user
1022961 request, channel = self.make_request(
1023962 "GET", self.url_other_user, access_token=self.admin_user_tok,
1024963 )
1025 self.render(request)
1026964
1027965 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
1028966 self.assertEqual("@user:test", channel.json_body["name"])
1043981 access_token=self.admin_user_tok,
1044982 content=body.encode(encoding="utf_8"),
1045983 )
1046 self.render(request)
1047984
1048985 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
1049986 self.assertEqual("@user:test", channel.json_body["name"])
1053990 request, channel = self.make_request(
1054991 "GET", self.url_other_user, access_token=self.admin_user_tok,
1055992 )
1056 self.render(request)
1057993
1058994 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
1059995 self.assertEqual("@user:test", channel.json_body["name"])
10751011 access_token=self.admin_user_tok,
10761012 content=body.encode(encoding="utf_8"),
10771013 )
1078 self.render(request)
10791014
10801015 self.assertEqual(201, int(channel.result["code"]), msg=channel.result["body"])
10811016 self.assertEqual("@bob:test", channel.json_body["name"])
10851020 request, channel = self.make_request(
10861021 "GET", url, access_token=self.admin_user_tok,
10871022 )
1088 self.render(request)
10891023
10901024 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
10911025 self.assertEqual("@bob:test", channel.json_body["name"])
11011035 access_token=self.admin_user_tok,
11021036 content=body.encode(encoding="utf_8"),
11031037 )
1104 self.render(request)
11051038
11061039 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
11071040
11091042 request, channel = self.make_request(
11101043 "GET", url, access_token=self.admin_user_tok,
11111044 )
1112 self.render(request)
11131045
11141046 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
11151047 self.assertEqual("@bob:test", channel.json_body["name"])
11521084 Try to list rooms of an user without authentication.
11531085 """
11541086 request, channel = self.make_request("GET", self.url, b"{}")
1155 self.render(request)
11561087
11571088 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
11581089 self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
11661097 request, channel = self.make_request(
11671098 "GET", self.url, access_token=other_user_token,
11681099 )
1169 self.render(request)
11701100
11711101 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
11721102 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
11791109 request, channel = self.make_request(
11801110 "GET", url, access_token=self.admin_user_tok,
11811111 )
1182 self.render(request)
11831112
11841113 self.assertEqual(404, channel.code, msg=channel.json_body)
11851114 self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
11931122 request, channel = self.make_request(
11941123 "GET", url, access_token=self.admin_user_tok,
11951124 )
1196 self.render(request)
11971125
11981126 self.assertEqual(400, channel.code, msg=channel.json_body)
11991127 self.assertEqual("Can only lookup local users", channel.json_body["error"])
12071135 request, channel = self.make_request(
12081136 "GET", self.url, access_token=self.admin_user_tok,
12091137 )
1210 self.render(request)
12111138
12121139 self.assertEqual(200, channel.code, msg=channel.json_body)
12131140 self.assertEqual(0, channel.json_body["total"])
12271154 request, channel = self.make_request(
12281155 "GET", self.url, access_token=self.admin_user_tok,
12291156 )
1230 self.render(request)
12311157
12321158 self.assertEqual(200, channel.code, msg=channel.json_body)
12331159 self.assertEqual(number_rooms, channel.json_body["total"])
12571183 Try to list pushers of an user without authentication.
12581184 """
12591185 request, channel = self.make_request("GET", self.url, b"{}")
1260 self.render(request)
12611186
12621187 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
12631188 self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
12711196 request, channel = self.make_request(
12721197 "GET", self.url, access_token=other_user_token,
12731198 )
1274 self.render(request)
12751199
12761200 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
12771201 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
12841208 request, channel = self.make_request(
12851209 "GET", url, access_token=self.admin_user_tok,
12861210 )
1287 self.render(request)
12881211
12891212 self.assertEqual(404, channel.code, msg=channel.json_body)
12901213 self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
12981221 request, channel = self.make_request(
12991222 "GET", url, access_token=self.admin_user_tok,
13001223 )
1301 self.render(request)
13021224
13031225 self.assertEqual(400, channel.code, msg=channel.json_body)
13041226 self.assertEqual("Can only lookup local users", channel.json_body["error"])
13121234 request, channel = self.make_request(
13131235 "GET", self.url, access_token=self.admin_user_tok,
13141236 )
1315 self.render(request)
13161237
13171238 self.assertEqual(200, channel.code, msg=channel.json_body)
13181239 self.assertEqual(0, channel.json_body["total"])
13421263 request, channel = self.make_request(
13431264 "GET", self.url, access_token=self.admin_user_tok,
13441265 )
1345 self.render(request)
13461266
13471267 self.assertEqual(200, channel.code, msg=channel.json_body)
13481268 self.assertEqual(1, channel.json_body["total"])
13821302 Try to list media of an user without authentication.
13831303 """
13841304 request, channel = self.make_request("GET", self.url, b"{}")
1385 self.render(request)
13861305
13871306 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
13881307 self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
13961315 request, channel = self.make_request(
13971316 "GET", self.url, access_token=other_user_token,
13981317 )
1399 self.render(request)
14001318
14011319 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
14021320 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
14091327 request, channel = self.make_request(
14101328 "GET", url, access_token=self.admin_user_tok,
14111329 )
1412 self.render(request)
14131330
14141331 self.assertEqual(404, channel.code, msg=channel.json_body)
14151332 self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
14231340 request, channel = self.make_request(
14241341 "GET", url, access_token=self.admin_user_tok,
14251342 )
1426 self.render(request)
14271343
14281344 self.assertEqual(400, channel.code, msg=channel.json_body)
14291345 self.assertEqual("Can only lookup local users", channel.json_body["error"])
14401356 request, channel = self.make_request(
14411357 "GET", self.url + "?limit=5", access_token=self.admin_user_tok,
14421358 )
1443 self.render(request)
14441359
14451360 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
14461361 self.assertEqual(channel.json_body["total"], number_media)
14601375 request, channel = self.make_request(
14611376 "GET", self.url + "?from=5", access_token=self.admin_user_tok,
14621377 )
1463 self.render(request)
14641378
14651379 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
14661380 self.assertEqual(channel.json_body["total"], number_media)
14801394 request, channel = self.make_request(
14811395 "GET", self.url + "?from=5&limit=10", access_token=self.admin_user_tok,
14821396 )
1483 self.render(request)
14841397
14851398 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
14861399 self.assertEqual(channel.json_body["total"], number_media)
14961409 request, channel = self.make_request(
14971410 "GET", self.url + "?limit=-5", access_token=self.admin_user_tok,
14981411 )
1499 self.render(request)
15001412
15011413 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
15021414 self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
15091421 request, channel = self.make_request(
15101422 "GET", self.url + "?from=-5", access_token=self.admin_user_tok,
15111423 )
1512 self.render(request)
15131424
15141425 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
15151426 self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
15281439 request, channel = self.make_request(
15291440 "GET", self.url + "?limit=20", access_token=self.admin_user_tok,
15301441 )
1531 self.render(request)
15321442
15331443 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
15341444 self.assertEqual(channel.json_body["total"], number_media)
15401450 request, channel = self.make_request(
15411451 "GET", self.url + "?limit=21", access_token=self.admin_user_tok,
15421452 )
1543 self.render(request)
15441453
15451454 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
15461455 self.assertEqual(channel.json_body["total"], number_media)
15521461 request, channel = self.make_request(
15531462 "GET", self.url + "?limit=19", access_token=self.admin_user_tok,
15541463 )
1555 self.render(request)
15561464
15571465 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
15581466 self.assertEqual(channel.json_body["total"], number_media)
15651473 request, channel = self.make_request(
15661474 "GET", self.url + "?from=19", access_token=self.admin_user_tok,
15671475 )
1568 self.render(request)
15691476
15701477 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
15711478 self.assertEqual(channel.json_body["total"], number_media)
15811488 request, channel = self.make_request(
15821489 "GET", self.url, access_token=self.admin_user_tok,
15831490 )
1584 self.render(request)
15851491
15861492 self.assertEqual(200, channel.code, msg=channel.json_body)
15871493 self.assertEqual(0, channel.json_body["total"])
15991505 request, channel = self.make_request(
16001506 "GET", self.url, access_token=self.admin_user_tok,
16011507 )
1602 self.render(request)
16031508
16041509 self.assertEqual(200, channel.code, msg=channel.json_body)
16051510 self.assertEqual(number_media, channel.json_body["total"])
16371542 self.assertIn("last_access_ts", m)
16381543 self.assertIn("quarantined_by", m)
16391544 self.assertIn("safe_from_quarantine", m)
1545
1546
1547 class UserTokenRestTestCase(unittest.HomeserverTestCase):
1548 """Test for /_synapse/admin/v1/users/<user>/login
1549 """
1550
1551 servlets = [
1552 synapse.rest.admin.register_servlets,
1553 login.register_servlets,
1554 sync.register_servlets,
1555 room.register_servlets,
1556 devices.register_servlets,
1557 logout.register_servlets,
1558 ]
1559
1560 def prepare(self, reactor, clock, hs):
1561 self.store = hs.get_datastore()
1562
1563 self.admin_user = self.register_user("admin", "pass", admin=True)
1564 self.admin_user_tok = self.login("admin", "pass")
1565
1566 self.other_user = self.register_user("user", "pass")
1567 self.other_user_tok = self.login("user", "pass")
1568 self.url = "/_synapse/admin/v1/users/%s/login" % urllib.parse.quote(
1569 self.other_user
1570 )
1571
1572 def _get_token(self) -> str:
1573 request, channel = self.make_request(
1574 "POST", self.url, b"{}", access_token=self.admin_user_tok
1575 )
1576 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
1577 return channel.json_body["access_token"]
1578
1579 def test_no_auth(self):
1580 """Try to login as a user without authentication.
1581 """
1582 request, channel = self.make_request("POST", self.url, b"{}")
1583
1584 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
1585 self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
1586
1587 def test_not_admin(self):
1588 """Try to login as a user as a non-admin user.
1589 """
1590 request, channel = self.make_request(
1591 "POST", self.url, b"{}", access_token=self.other_user_tok
1592 )
1593
1594 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
1595
1596 def test_send_event(self):
1597 """Test that sending event as a user works.
1598 """
1599 # Create a room.
1600 room_id = self.helper.create_room_as(self.other_user, tok=self.other_user_tok)
1601
1602 # Login in as the user
1603 puppet_token = self._get_token()
1604
1605 # Test that sending works, and generates the event as the right user.
1606 resp = self.helper.send_event(room_id, "com.example.test", tok=puppet_token)
1607 event_id = resp["event_id"]
1608 event = self.get_success(self.store.get_event(event_id))
1609 self.assertEqual(event.sender, self.other_user)
1610
1611 def test_devices(self):
1612 """Tests that logging in as a user doesn't create a new device for them.
1613 """
1614 # Login in as the user
1615 self._get_token()
1616
1617 # Check that we don't see a new device in our devices list
1618 request, channel = self.make_request(
1619 "GET", "devices", b"{}", access_token=self.other_user_tok
1620 )
1621 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
1622
1623 # We should only see the one device (from the login in `prepare`)
1624 self.assertEqual(len(channel.json_body["devices"]), 1)
1625
1626 def test_logout(self):
1627 """Test that calling `/logout` with the token works.
1628 """
1629 # Login in as the user
1630 puppet_token = self._get_token()
1631
1632 # Test that we can successfully make a request
1633 request, channel = self.make_request(
1634 "GET", "devices", b"{}", access_token=puppet_token
1635 )
1636 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
1637
1638 # Logout with the puppet token
1639 request, channel = self.make_request(
1640 "POST", "logout", b"{}", access_token=puppet_token
1641 )
1642 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
1643
1644 # The puppet token should no longer work
1645 request, channel = self.make_request(
1646 "GET", "devices", b"{}", access_token=puppet_token
1647 )
1648 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
1649
1650 # .. but the real user's tokens should still work
1651 request, channel = self.make_request(
1652 "GET", "devices", b"{}", access_token=self.other_user_tok
1653 )
1654 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
1655
1656 def test_user_logout_all(self):
1657 """Tests that the target user calling `/logout/all` does *not* expire
1658 the token.
1659 """
1660 # Login in as the user
1661 puppet_token = self._get_token()
1662
1663 # Test that we can successfully make a request
1664 request, channel = self.make_request(
1665 "GET", "devices", b"{}", access_token=puppet_token
1666 )
1667 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
1668
1669 # Logout all with the real user token
1670 request, channel = self.make_request(
1671 "POST", "logout/all", b"{}", access_token=self.other_user_tok
1672 )
1673 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
1674
1675 # The puppet token should still work
1676 request, channel = self.make_request(
1677 "GET", "devices", b"{}", access_token=puppet_token
1678 )
1679 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
1680
1681 # .. but the real user's tokens shouldn't
1682 request, channel = self.make_request(
1683 "GET", "devices", b"{}", access_token=self.other_user_tok
1684 )
1685 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
1686
1687 def test_admin_logout_all(self):
1688 """Tests that the admin user calling `/logout/all` does expire the
1689 token.
1690 """
1691 # Login in as the user
1692 puppet_token = self._get_token()
1693
1694 # Test that we can successfully make a request
1695 request, channel = self.make_request(
1696 "GET", "devices", b"{}", access_token=puppet_token
1697 )
1698 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
1699
1700 # Logout all with the admin user token
1701 request, channel = self.make_request(
1702 "POST", "logout/all", b"{}", access_token=self.admin_user_tok
1703 )
1704 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
1705
1706 # The puppet token should no longer work
1707 request, channel = self.make_request(
1708 "GET", "devices", b"{}", access_token=puppet_token
1709 )
1710 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
1711
1712 # .. but the real user's tokens should still work
1713 request, channel = self.make_request(
1714 "GET", "devices", b"{}", access_token=self.other_user_tok
1715 )
1716 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
1717
1718 @unittest.override_config(
1719 {
1720 "public_baseurl": "https://example.org/",
1721 "user_consent": {
1722 "version": "1.0",
1723 "policy_name": "My Cool Privacy Policy",
1724 "template_dir": "/",
1725 "require_at_registration": True,
1726 "block_events_error": "You should accept the policy",
1727 },
1728 "form_secret": "123secret",
1729 }
1730 )
1731 def test_consent(self):
1732 """Test that sending a message is not subject to the privacy policies.
1733 """
1734 # Have the admin user accept the terms.
1735 self.get_success(self.store.user_set_consent_version(self.admin_user, "1.0"))
1736
1737 # First, cheekily accept the terms and create a room
1738 self.get_success(self.store.user_set_consent_version(self.other_user, "1.0"))
1739 room_id = self.helper.create_room_as(self.other_user, tok=self.other_user_tok)
1740 self.helper.send_event(room_id, "com.example.test", tok=self.other_user_tok)
1741
1742 # Now unaccept it and check that we can't send an event
1743 self.get_success(self.store.user_set_consent_version(self.other_user, "0.0"))
1744 self.helper.send_event(
1745 room_id, "com.example.test", tok=self.other_user_tok, expect_code=403
1746 )
1747
1748 # Login in as the user
1749 puppet_token = self._get_token()
1750
1751 # Sending an event on their behalf should work fine
1752 self.helper.send_event(room_id, "com.example.test", tok=puppet_token)
1753
1754 @override_config(
1755 {"limit_usage_by_mau": True, "max_mau_value": 1, "mau_trial_days": 0}
1756 )
1757 def test_mau_limit(self):
1758 # Create a room as the admin user. This will bump the monthly active users to 1.
1759 room_id = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)
1760
1761 # Trying to join as the other user should fail due to reaching MAU limit.
1762 self.helper.join(
1763 room_id, user=self.other_user, tok=self.other_user_tok, expect_code=403
1764 )
1765
1766 # Logging in as the other user and joining a room should work, even
1767 # though the MAU limit would stop the user doing so.
1768 puppet_token = self._get_token()
1769 self.helper.join(room_id, user=self.other_user, tok=puppet_token)
1770
1771
1772 class WhoisRestTestCase(unittest.HomeserverTestCase):
1773
1774 servlets = [
1775 synapse.rest.admin.register_servlets,
1776 login.register_servlets,
1777 ]
1778
1779 def prepare(self, reactor, clock, hs):
1780 self.store = hs.get_datastore()
1781
1782 self.admin_user = self.register_user("admin", "pass", admin=True)
1783 self.admin_user_tok = self.login("admin", "pass")
1784
1785 self.other_user = self.register_user("user", "pass")
1786 self.url1 = "/_synapse/admin/v1/whois/%s" % urllib.parse.quote(self.other_user)
1787 self.url2 = "/_matrix/client/r0/admin/whois/%s" % urllib.parse.quote(
1788 self.other_user
1789 )
1790
1791 def test_no_auth(self):
1792 """
1793 Try to get information of an user without authentication.
1794 """
1795 request, channel = self.make_request("GET", self.url1, b"{}")
1796 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
1797 self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
1798
1799 request, channel = self.make_request("GET", self.url2, b"{}")
1800 self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
1801 self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
1802
1803 def test_requester_is_not_admin(self):
1804 """
1805 If the user is not a server admin, an error is returned.
1806 """
1807 self.register_user("user2", "pass")
1808 other_user2_token = self.login("user2", "pass")
1809
1810 request, channel = self.make_request(
1811 "GET", self.url1, access_token=other_user2_token,
1812 )
1813 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
1814 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
1815
1816 request, channel = self.make_request(
1817 "GET", self.url2, access_token=other_user2_token,
1818 )
1819 self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
1820 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
1821
1822 def test_user_is_not_local(self):
1823 """
1824 Tests that a lookup for a user that is not a local returns a 400
1825 """
1826 url1 = "/_synapse/admin/v1/whois/@unknown_person:unknown_domain"
1827 url2 = "/_matrix/client/r0/admin/whois/@unknown_person:unknown_domain"
1828
1829 request, channel = self.make_request(
1830 "GET", url1, access_token=self.admin_user_tok,
1831 )
1832 self.assertEqual(400, channel.code, msg=channel.json_body)
1833 self.assertEqual("Can only whois a local user", channel.json_body["error"])
1834
1835 request, channel = self.make_request(
1836 "GET", url2, access_token=self.admin_user_tok,
1837 )
1838 self.assertEqual(400, channel.code, msg=channel.json_body)
1839 self.assertEqual("Can only whois a local user", channel.json_body["error"])
1840
1841 def test_get_whois_admin(self):
1842 """
1843 The lookup should succeed for an admin.
1844 """
1845 request, channel = self.make_request(
1846 "GET", self.url1, access_token=self.admin_user_tok,
1847 )
1848 self.assertEqual(200, channel.code, msg=channel.json_body)
1849 self.assertEqual(self.other_user, channel.json_body["user_id"])
1850 self.assertIn("devices", channel.json_body)
1851
1852 request, channel = self.make_request(
1853 "GET", self.url2, access_token=self.admin_user_tok,
1854 )
1855 self.assertEqual(200, channel.code, msg=channel.json_body)
1856 self.assertEqual(self.other_user, channel.json_body["user_id"])
1857 self.assertIn("devices", channel.json_body)
1858
1859 def test_get_whois_user(self):
1860 """
1861 The lookup should succeed for a normal user looking up their own information.
1862 """
1863 other_user_token = self.login("user", "pass")
1864
1865 request, channel = self.make_request(
1866 "GET", self.url1, access_token=other_user_token,
1867 )
1868 self.assertEqual(200, channel.code, msg=channel.json_body)
1869 self.assertEqual(self.other_user, channel.json_body["user_id"])
1870 self.assertIn("devices", channel.json_body)
1871
1872 request, channel = self.make_request(
1873 "GET", self.url2, access_token=other_user_token,
1874 )
1875 self.assertEqual(200, channel.code, msg=channel.json_body)
1876 self.assertEqual(self.other_user, channel.json_body["user_id"])
1877 self.assertIn("devices", channel.json_body)
2020 from synapse.rest.consent import consent_resource
2121
2222 from tests import unittest
23 from tests.server import render
23 from tests.server import FakeSite, make_request
2424
2525
2626 class ConsentResourceTestCase(unittest.HomeserverTestCase):
6060 def test_render_public_consent(self):
6161 """You can observe the terms form without specifying a user"""
6262 resource = consent_resource.ConsentResource(self.hs)
63 request, channel = self.make_request("GET", "/consent?v=1", shorthand=False)
64 render(request, resource, self.reactor)
63 request, channel = make_request(
64 self.reactor, FakeSite(resource), "GET", "/consent?v=1", shorthand=False
65 )
6566 self.assertEqual(channel.code, 200)
6667
6768 def test_accept_consent(self):
8081 uri_builder.build_user_consent_uri(user_id).replace("_matrix/", "")
8182 + "&u=user"
8283 )
83 request, channel = self.make_request(
84 "GET", consent_uri, access_token=access_token, shorthand=False
84 request, channel = make_request(
85 self.reactor,
86 FakeSite(resource),
87 "GET",
88 consent_uri,
89 access_token=access_token,
90 shorthand=False,
8591 )
86 render(request, resource, self.reactor)
8792 self.assertEqual(channel.code, 200)
8893
8994 # Get the version from the body, and whether we've consented
9196 self.assertEqual(consented, "False")
9297
9398 # POST to the consent page, saying we've agreed
94 request, channel = self.make_request(
99 request, channel = make_request(
100 self.reactor,
101 FakeSite(resource),
95102 "POST",
96103 consent_uri + "&v=" + version,
97104 access_token=access_token,
98105 shorthand=False,
99106 )
100 render(request, resource, self.reactor)
101107 self.assertEqual(channel.code, 200)
102108
103109 # Fetch the consent page, to get the consent version -- it should have
104110 # changed
105 request, channel = self.make_request(
106 "GET", consent_uri, access_token=access_token, shorthand=False
111 request, channel = make_request(
112 self.reactor,
113 FakeSite(resource),
114 "GET",
115 consent_uri,
116 access_token=access_token,
117 shorthand=False,
107118 )
108 render(request, resource, self.reactor)
109119 self.assertEqual(channel.code, 200)
110120
111121 # Get the version from the body, and check that it's the version we
9393 url = "/_matrix/client/r0/rooms/%s/event/%s" % (room_id, event_id)
9494
9595 request, channel = self.make_request("GET", url)
96 self.render(request)
9796
9897 self.assertEqual(channel.code, expected_code, channel.result)
9998
4545 request, channel = self.make_request(
4646 b"POST", "/createRoom", b"{}", access_token=tok
4747 )
48 self.render(request)
4948 self.assertEquals(channel.result["code"], b"200", channel.result)
5049 room_id = channel.json_body["room_id"]
5150
5958 request, channel = self.make_request(
6059 b"POST", request_url, request_data, access_token=tok
6160 )
62 self.render(request)
6361 self.assertEquals(channel.result["code"], b"403", channel.result)
7171 request, channel = self.make_request(
7272 "POST", path, content={}, access_token=access_token
7373 )
74 self.render(request)
7574 self.assertEqual(int(channel.result["code"]), expect_code)
7675 return channel.json_body
7776
7978 request, channel = self.make_request(
8079 "GET", "sync", access_token=self.mod_access_token
8180 )
82 self.render(request)
8381 self.assertEqual(channel.result["code"], b"200")
8482 room_sync = channel.json_body["rooms"]["join"][room_id]
8583 return room_sync["timeline"]["events"]
325325 url = "/_matrix/client/r0/rooms/%s/event/%s" % (room_id, event_id)
326326
327327 request, channel = self.make_request("GET", url, access_token=self.token)
328 self.render(request)
329328
330329 self.assertEqual(channel.code, expected_code, channel.result)
331330
9494 {"id_server": "test", "medium": "email", "address": "test@test.test"},
9595 access_token=self.banned_access_token,
9696 )
97 self.render(request)
9897 self.assertEquals(200, channel.code, channel.result)
9998
10099 # This should have raised an error earlier, but double check this wasn't called.
109108 {"visibility": "public", "invite": [self.other_user_id]},
110109 access_token=self.banned_access_token,
111110 )
112 self.render(request)
113111 self.assertEquals(200, channel.code, channel.result)
114112 room_id = channel.json_body["room_id"]
115113
165163 {"new_version": "6"},
166164 access_token=self.banned_access_token,
167165 )
168 self.render(request)
169166 self.assertEquals(200, channel.code, channel.result)
170167 # A new room_id should be returned.
171168 self.assertIn("replacement_room", channel.json_body)
191188 {"typing": True, "timeout": 30000},
192189 access_token=self.banned_access_token,
193190 )
194 self.render(request)
195191 self.assertEquals(200, channel.code)
196192
197193 # There should be no typing events.
207203 {"typing": True, "timeout": 30000},
208204 access_token=self.other_access_token,
209205 )
210 self.render(request)
211206 self.assertEquals(200, channel.code)
212207
213208 # These appear in the room.
254249 {"displayname": new_display_name},
255250 access_token=self.banned_access_token,
256251 )
257 self.render(request)
258252 self.assertEquals(200, channel.code, channel.result)
259253 self.assertEqual(channel.json_body, {})
260254
262256 request, channel = self.make_request(
263257 "GET", "/profile/%s/displayname" % (self.banned_user_id,)
264258 )
265 self.render(request)
266259 self.assertEqual(channel.code, 200, channel.result)
267260 self.assertEqual(channel.json_body["displayname"], new_display_name)
268261
295288 {"membership": "join", "displayname": new_display_name},
296289 access_token=self.banned_access_token,
297290 )
298 self.render(request)
299291 self.assertEquals(200, channel.code, channel.result)
300292 self.assertIn("event_id", channel.json_body)
301293
9191 {},
9292 access_token=self.tok,
9393 )
94 self.render(request)
9594 self.assertEquals(channel.result["code"], b"200", channel.result)
9695
9796 callback.assert_called_once()
110109 {},
111110 access_token=self.tok,
112111 )
113 self.render(request)
114112 self.assertEquals(channel.result["code"], b"403", channel.result)
115113
116114 def test_cannot_modify_event(self):
130128 {"x": "x"},
131129 access_token=self.tok,
132130 )
133 self.render(request)
134131 self.assertEqual(channel.result["code"], b"500", channel.result)
135132
136133 def test_modify_event(self):
150147 {"x": "x"},
151148 access_token=self.tok,
152149 )
153 self.render(request)
154150 self.assertEqual(channel.result["code"], b"200", channel.result)
155151 event_id = channel.json_body["event_id"]
156152
160156 "/_matrix/client/r0/rooms/%s/event/%s" % (self.room_id, event_id),
161157 access_token=self.tok,
162158 )
163 self.render(request)
164159 self.assertEqual(channel.result["code"], b"200", channel.result)
165160 ev = channel.json_body
166161 self.assertEqual(ev["content"]["x"], "y")
9393 request, channel = self.make_request(
9494 "POST", url, request_data, access_token=self.user_tok
9595 )
96 self.render(request)
9796 self.assertEqual(channel.code, 400, channel.result)
9897
9998 def test_room_creation(self):
107106 request, channel = self.make_request(
108107 "POST", url, request_data, access_token=self.user_tok
109108 )
110 self.render(request)
111109 self.assertEqual(channel.code, 200, channel.result)
112110
113111 def set_alias_via_state_event(self, expected_code, alias_length=5):
122120 request, channel = self.make_request(
123121 "PUT", url, request_data, access_token=self.user_tok
124122 )
125 self.render(request)
126123 self.assertEqual(channel.code, expected_code, channel.result)
127124
128125 def set_alias_via_directory(self, expected_code, alias_length=5):
133130 request, channel = self.make_request(
134131 "PUT", url, request_data, access_token=self.user_tok
135132 )
136 self.render(request)
137133 self.assertEqual(channel.code, expected_code, channel.result)
138134
139135 def random_alias(self, length):
6565 request, channel = self.make_request(
6666 "GET", "/events?access_token=%s" % ("invalid" + self.token,)
6767 )
68 self.render(request)
6968 self.assertEquals(channel.code, 401, msg=channel.result)
7069
7170 # valid token, expect content
7271 request, channel = self.make_request(
7372 "GET", "/events?access_token=%s&timeout=0" % (self.token,)
7473 )
75 self.render(request)
7674 self.assertEquals(channel.code, 200, msg=channel.result)
7775 self.assertTrue("chunk" in channel.json_body)
7876 self.assertTrue("start" in channel.json_body)
9189 request, channel = self.make_request(
9290 "GET", "/events?access_token=%s&timeout=0" % (self.token,)
9391 )
94 self.render(request)
9592 self.assertEquals(channel.code, 200, msg=channel.result)
9693
9794 # We may get a presence event for ourselves down
154151 request, channel = self.make_request(
155152 "GET", "/events/" + event_id, access_token=self.token,
156153 )
157 self.render(request)
158154 self.assertEquals(channel.code, 200, msg=channel.result)
6363 "password": "monkey",
6464 }
6565 request, channel = self.make_request(b"POST", LOGIN_URL, params)
66 self.render(request)
6766
6867 if i == 5:
6968 self.assertEquals(channel.result["code"], b"429", channel.result)
8382 "password": "monkey",
8483 }
8584 request, channel = self.make_request(b"POST", LOGIN_URL, params)
86 self.render(request)
8785
8886 self.assertEquals(channel.result["code"], b"200", channel.result)
8987
110108 "password": "monkey",
111109 }
112110 request, channel = self.make_request(b"POST", LOGIN_URL, params)
113 self.render(request)
114111
115112 if i == 5:
116113 self.assertEquals(channel.result["code"], b"429", channel.result)
130127 "password": "monkey",
131128 }
132129 request, channel = self.make_request(b"POST", LOGIN_URL, params)
133 self.render(request)
134130
135131 self.assertEquals(channel.result["code"], b"200", channel.result)
136132
157153 "password": "notamonkey",
158154 }
159155 request, channel = self.make_request(b"POST", LOGIN_URL, params)
160 self.render(request)
161156
162157 if i == 5:
163158 self.assertEquals(channel.result["code"], b"429", channel.result)
177172 "password": "notamonkey",
178173 }
179174 request, channel = self.make_request(b"POST", LOGIN_URL, params)
180 self.render(request)
181175
182176 self.assertEquals(channel.result["code"], b"403", channel.result)
183177
187181
188182 # we shouldn't be able to make requests without an access token
189183 request, channel = self.make_request(b"GET", TEST_URL)
190 self.render(request)
191184 self.assertEquals(channel.result["code"], b"401", channel.result)
192185 self.assertEquals(channel.json_body["errcode"], "M_MISSING_TOKEN")
193186
198191 "password": "monkey",
199192 }
200193 request, channel = self.make_request(b"POST", LOGIN_URL, params)
201 self.render(request)
202194
203195 self.assertEquals(channel.code, 200, channel.result)
204196 access_token = channel.json_body["access_token"]
208200 request, channel = self.make_request(
209201 b"GET", TEST_URL, access_token=access_token
210202 )
211 self.render(request)
212203 self.assertEquals(channel.code, 200, channel.result)
213204
214205 # time passes
218209 request, channel = self.make_request(
219210 b"GET", TEST_URL, access_token=access_token
220211 )
221 self.render(request)
222212 self.assertEquals(channel.code, 401, channel.result)
223213 self.assertEquals(channel.json_body["errcode"], "M_UNKNOWN_TOKEN")
224214 self.assertEquals(channel.json_body["soft_logout"], True)
235225 request, channel = self.make_request(
236226 b"GET", TEST_URL, access_token=access_token
237227 )
238 self.render(request)
239228 self.assertEquals(channel.code, 401, channel.result)
240229 self.assertEquals(channel.json_body["errcode"], "M_UNKNOWN_TOKEN")
241230 self.assertEquals(channel.json_body["soft_logout"], True)
246235 request, channel = self.make_request(
247236 b"GET", TEST_URL, access_token=access_token
248237 )
249 self.render(request)
250238 self.assertEquals(channel.code, 401, channel.result)
251239 self.assertEquals(channel.json_body["errcode"], "M_UNKNOWN_TOKEN")
252240 self.assertEquals(channel.json_body["soft_logout"], False)
256244 request, channel = self.make_request(
257245 b"DELETE", "devices/" + device_id, access_token=access_token
258246 )
259 self.render(request)
260247 self.assertEquals(channel.code, 401, channel.result)
261248 # check it's a UI-Auth fail
262249 self.assertEqual(
280267 access_token=access_token,
281268 content={"auth": auth},
282269 )
283 self.render(request)
284270 self.assertEquals(channel.code, 200, channel.result)
285271
286272 @override_config({"session_lifetime": "24h"})
294280 request, channel = self.make_request(
295281 b"GET", TEST_URL, access_token=access_token
296282 )
297 self.render(request)
298283 self.assertEquals(channel.code, 200, channel.result)
299284
300285 # time passes
304289 request, channel = self.make_request(
305290 b"GET", TEST_URL, access_token=access_token
306291 )
307 self.render(request)
308292 self.assertEquals(channel.code, 401, channel.result)
309293 self.assertEquals(channel.json_body["errcode"], "M_UNKNOWN_TOKEN")
310294 self.assertEquals(channel.json_body["soft_logout"], True)
313297 request, channel = self.make_request(
314298 b"POST", "/logout", access_token=access_token
315299 )
316 self.render(request)
317300 self.assertEquals(channel.result["code"], b"200", channel.result)
318301
319302 @override_config({"session_lifetime": "24h"})
327310 request, channel = self.make_request(
328311 b"GET", TEST_URL, access_token=access_token
329312 )
330 self.render(request)
331313 self.assertEquals(channel.code, 200, channel.result)
332314
333315 # time passes
337319 request, channel = self.make_request(
338320 b"GET", TEST_URL, access_token=access_token
339321 )
340 self.render(request)
341322 self.assertEquals(channel.code, 401, channel.result)
342323 self.assertEquals(channel.json_body["errcode"], "M_UNKNOWN_TOKEN")
343324 self.assertEquals(channel.json_body["soft_logout"], True)
346327 request, channel = self.make_request(
347328 b"POST", "/logout/all", access_token=access_token
348329 )
349 self.render(request)
350330 self.assertEquals(channel.result["code"], b"200", channel.result)
351331
352332
422402
423403 # Get Synapse to call the fake CAS and serve the template.
424404 request, channel = self.make_request("GET", cas_ticket_url)
425 self.render(request)
426405
427406 # Test that the response is HTML.
428407 self.assertEqual(channel.code, 200)
467446
468447 # Get Synapse to call the fake CAS and serve the template.
469448 request, channel = self.make_request("GET", cas_ticket_url)
470 self.render(request)
471449
472450 self.assertEqual(channel.code, 302)
473451 location_headers = channel.headers.getRawHeaders("Location")
494472
495473 # Get Synapse to call the fake CAS and serve the template.
496474 request, channel = self.make_request("GET", cas_ticket_url)
497 self.render(request)
498475
499476 # Because the user is deactivated they are served an error template.
500477 self.assertEqual(channel.code, 403)
525502 {"type": "org.matrix.login.jwt", "token": self.jwt_encode(*args)}
526503 )
527504 request, channel = self.make_request(b"POST", LOGIN_URL, params)
528 self.render(request)
529505 return channel
530506
531507 def test_login_jwt_valid_registered(self):
658634 def test_login_no_token(self):
659635 params = json.dumps({"type": "org.matrix.login.jwt"})
660636 request, channel = self.make_request(b"POST", LOGIN_URL, params)
661 self.render(request)
662637 self.assertEqual(channel.result["code"], b"403", channel.result)
663638 self.assertEqual(channel.json_body["errcode"], "M_FORBIDDEN")
664639 self.assertEqual(channel.json_body["error"], "Token field for JWT is missing")
732707 {"type": "org.matrix.login.jwt", "token": self.jwt_encode(*args)}
733708 )
734709 request, channel = self.make_request(b"POST", LOGIN_URL, params)
735 self.render(request)
736710 return channel
737711
738712 def test_login_jwt_valid(self):
765739 "/_matrix/client/r0/register?access_token=%s" % (self.service.token,),
766740 {"username": username},
767741 )
768 self.render(request)
769742
770743 def make_homeserver(self, reactor, clock):
771744 self.hs = self.setup_test_homeserver()
814787 b"POST", LOGIN_URL, params, access_token=self.service.token
815788 )
816789
817 self.render(request)
818790 self.assertEquals(channel.result["code"], b"200", channel.result)
819791
820792 def test_login_appservice_user_bot(self):
830802 b"POST", LOGIN_URL, params, access_token=self.service.token
831803 )
832804
833 self.render(request)
834805 self.assertEquals(channel.result["code"], b"200", channel.result)
835806
836807 def test_login_appservice_wrong_user(self):
846817 b"POST", LOGIN_URL, params, access_token=self.service.token
847818 )
848819
849 self.render(request)
850820 self.assertEquals(channel.result["code"], b"403", channel.result)
851821
852822 def test_login_appservice_wrong_as(self):
862832 b"POST", LOGIN_URL, params, access_token=self.another_service.token
863833 )
864834
865 self.render(request)
866835 self.assertEquals(channel.result["code"], b"403", channel.result)
867836
868837 def test_login_appservice_no_token(self):
877846 }
878847 request, channel = self.make_request(b"POST", LOGIN_URL, params)
879848
880 self.render(request)
881849 self.assertEquals(channel.result["code"], b"401", channel.result)
3232
3333 def make_homeserver(self, reactor, clock):
3434
35 presence_handler = Mock()
36 presence_handler.set_state.return_value = defer.succeed(None)
37
3538 hs = self.setup_test_homeserver(
36 "red", http_client=None, federation_client=Mock()
39 "red",
40 http_client=None,
41 federation_client=Mock(),
42 presence_handler=presence_handler,
3743 )
38
39 hs.presence_handler = Mock()
40 hs.presence_handler.set_state.return_value = defer.succeed(None)
4144
4245 return hs
4346
5255 request, channel = self.make_request(
5356 "PUT", "/presence/%s/status" % (self.user_id,), body
5457 )
55 self.render(request)
5658
5759 self.assertEqual(channel.code, 200)
58 self.assertEqual(self.hs.presence_handler.set_state.call_count, 1)
60 self.assertEqual(self.hs.get_presence_handler().set_state.call_count, 1)
5961
6062 def test_put_presence_disabled(self):
6163 """
6870 request, channel = self.make_request(
6971 "PUT", "/presence/%s/status" % (self.user_id,), body
7072 )
71 self.render(request)
7273
7374 self.assertEqual(channel.code, 200)
74 self.assertEqual(self.hs.presence_handler.set_state.call_count, 0)
75 self.assertEqual(self.hs.get_presence_handler().set_state.call_count, 0)
194194 content=json.dumps({"displayname": "test"}),
195195 access_token=self.owner_tok,
196196 )
197 self.render(request)
198197 self.assertEqual(channel.code, 200, channel.result)
199198
200199 res = self.get_displayname()
208207 content=json.dumps({"displayname": "test" * 100}),
209208 access_token=self.owner_tok,
210209 )
211 self.render(request)
212210 self.assertEqual(channel.code, 400, channel.result)
213211
214212 res = self.get_displayname()
218216 request, channel = self.make_request(
219217 "GET", "/profile/%s/displayname" % (self.owner,)
220218 )
221 self.render(request)
222219 self.assertEqual(channel.code, 200, channel.result)
223220 return channel.json_body["displayname"]
224221
283280 request, channel = self.make_request(
284281 "GET", self.profile_url + url_suffix, access_token=access_token
285282 )
286 self.render(request)
287283 self.assertEqual(channel.code, expected_code, channel.result)
288284
289285 def ensure_requester_left_room(self):
326322 request, channel = self.make_request(
327323 "GET", "/profile/" + self.requester, access_token=self.requester_tok
328324 )
329 self.render(request)
330325 self.assertEqual(channel.code, 200, channel.result)
331326
332327 request, channel = self.make_request(
334329 "/profile/" + self.requester + "/displayname",
335330 access_token=self.requester_tok,
336331 )
337 self.render(request)
338332 self.assertEqual(channel.code, 200, channel.result)
339333
340334 request, channel = self.make_request(
342336 "/profile/" + self.requester + "/avatar_url",
343337 access_token=self.requester_tok,
344338 )
345 self.render(request)
346 self.assertEqual(channel.code, 200, channel.result)
339 self.assertEqual(channel.code, 200, channel.result)
4747 request, channel = self.make_request(
4848 "PUT", "/pushrules/global/override/best.friend", body, access_token=token
4949 )
50 self.render(request)
5150 self.assertEqual(channel.code, 200)
5251
5352 # GET enabled for that new rule
5453 request, channel = self.make_request(
5554 "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
5655 )
57 self.render(request)
5856 self.assertEqual(channel.code, 200)
5957 self.assertEqual(channel.json_body["enabled"], True)
6058
7876 request, channel = self.make_request(
7977 "PUT", "/pushrules/global/override/best.friend", body, access_token=token
8078 )
81 self.render(request)
8279 self.assertEqual(channel.code, 200)
8380
8481 # disable the rule
8885 {"enabled": False},
8986 access_token=token,
9087 )
91 self.render(request)
9288 self.assertEqual(channel.code, 200)
9389
9490 # check rule disabled
9591 request, channel = self.make_request(
9692 "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
9793 )
98 self.render(request)
9994 self.assertEqual(channel.code, 200)
10095 self.assertEqual(channel.json_body["enabled"], False)
10196
10398 request, channel = self.make_request(
10499 "DELETE", "/pushrules/global/override/best.friend", access_token=token
105100 )
106 self.render(request)
107 self.assertEqual(channel.code, 200)
108
109 # PUT a new rule
110 request, channel = self.make_request(
111 "PUT", "/pushrules/global/override/best.friend", body, access_token=token
112 )
113 self.render(request)
101 self.assertEqual(channel.code, 200)
102
103 # PUT a new rule
104 request, channel = self.make_request(
105 "PUT", "/pushrules/global/override/best.friend", body, access_token=token
106 )
114107 self.assertEqual(channel.code, 200)
115108
116109 # GET enabled for that new rule
117110 request, channel = self.make_request(
118111 "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
119112 )
120 self.render(request)
121113 self.assertEqual(channel.code, 200)
122114 self.assertEqual(channel.json_body["enabled"], True)
123115
140132 request, channel = self.make_request(
141133 "PUT", "/pushrules/global/override/best.friend", body, access_token=token
142134 )
143 self.render(request)
144135 self.assertEqual(channel.code, 200)
145136
146137 # disable the rule
150141 {"enabled": False},
151142 access_token=token,
152143 )
153 self.render(request)
154144 self.assertEqual(channel.code, 200)
155145
156146 # check rule disabled
157147 request, channel = self.make_request(
158148 "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
159149 )
160 self.render(request)
161150 self.assertEqual(channel.code, 200)
162151 self.assertEqual(channel.json_body["enabled"], False)
163152
168157 {"enabled": True},
169158 access_token=token,
170159 )
171 self.render(request)
172160 self.assertEqual(channel.code, 200)
173161
174162 # check rule enabled
175163 request, channel = self.make_request(
176164 "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
177165 )
178 self.render(request)
179166 self.assertEqual(channel.code, 200)
180167 self.assertEqual(channel.json_body["enabled"], True)
181168
197184 request, channel = self.make_request(
198185 "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
199186 )
200 self.render(request)
201 self.assertEqual(channel.code, 404)
202 self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
203
204 # PUT a new rule
205 request, channel = self.make_request(
206 "PUT", "/pushrules/global/override/best.friend", body, access_token=token
207 )
208 self.render(request)
187 self.assertEqual(channel.code, 404)
188 self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
189
190 # PUT a new rule
191 request, channel = self.make_request(
192 "PUT", "/pushrules/global/override/best.friend", body, access_token=token
193 )
209194 self.assertEqual(channel.code, 200)
210195
211196 # GET enabled for that new rule
212197 request, channel = self.make_request(
213198 "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
214199 )
215 self.render(request)
216200 self.assertEqual(channel.code, 200)
217201
218202 # DELETE the rule
219203 request, channel = self.make_request(
220204 "DELETE", "/pushrules/global/override/best.friend", access_token=token
221205 )
222 self.render(request)
223206 self.assertEqual(channel.code, 200)
224207
225208 # check 404 for deleted rule
226209 request, channel = self.make_request(
227210 "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
228211 )
229 self.render(request)
230212 self.assertEqual(channel.code, 404)
231213 self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
232214
241223 request, channel = self.make_request(
242224 "GET", "/pushrules/global/override/.m.muahahaha/enabled", access_token=token
243225 )
244 self.render(request)
245226 self.assertEqual(channel.code, 404)
246227 self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
247228
259240 {"enabled": True},
260241 access_token=token,
261242 )
262 self.render(request)
263243 self.assertEqual(channel.code, 404)
264244 self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
265245
277257 {"enabled": True},
278258 access_token=token,
279259 )
280 self.render(request)
281260 self.assertEqual(channel.code, 404)
282261 self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
283262
299278 request, channel = self.make_request(
300279 "PUT", "/pushrules/global/override/best.friend", body, access_token=token
301280 )
302 self.render(request)
303281 self.assertEqual(channel.code, 200)
304282
305283 # GET actions for that new rule
306284 request, channel = self.make_request(
307285 "GET", "/pushrules/global/override/best.friend/actions", access_token=token
308286 )
309 self.render(request)
310287 self.assertEqual(channel.code, 200)
311288 self.assertEqual(
312289 channel.json_body["actions"], ["notify", {"set_tweak": "highlight"}]
330307 request, channel = self.make_request(
331308 "PUT", "/pushrules/global/override/best.friend", body, access_token=token
332309 )
333 self.render(request)
334310 self.assertEqual(channel.code, 200)
335311
336312 # change the rule actions
340316 {"actions": ["dont_notify"]},
341317 access_token=token,
342318 )
343 self.render(request)
344319 self.assertEqual(channel.code, 200)
345320
346321 # GET actions for that new rule
347322 request, channel = self.make_request(
348323 "GET", "/pushrules/global/override/best.friend/actions", access_token=token
349324 )
350 self.render(request)
351325 self.assertEqual(channel.code, 200)
352326 self.assertEqual(channel.json_body["actions"], ["dont_notify"])
353327
369343 request, channel = self.make_request(
370344 "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
371345 )
372 self.render(request)
373 self.assertEqual(channel.code, 404)
374 self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
375
376 # PUT a new rule
377 request, channel = self.make_request(
378 "PUT", "/pushrules/global/override/best.friend", body, access_token=token
379 )
380 self.render(request)
346 self.assertEqual(channel.code, 404)
347 self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
348
349 # PUT a new rule
350 request, channel = self.make_request(
351 "PUT", "/pushrules/global/override/best.friend", body, access_token=token
352 )
381353 self.assertEqual(channel.code, 200)
382354
383355 # DELETE the rule
384356 request, channel = self.make_request(
385357 "DELETE", "/pushrules/global/override/best.friend", access_token=token
386358 )
387 self.render(request)
388359 self.assertEqual(channel.code, 200)
389360
390361 # check 404 for deleted rule
391362 request, channel = self.make_request(
392363 "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
393364 )
394 self.render(request)
395365 self.assertEqual(channel.code, 404)
396366 self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
397367
406376 request, channel = self.make_request(
407377 "GET", "/pushrules/global/override/.m.muahahaha/actions", access_token=token
408378 )
409 self.render(request)
410379 self.assertEqual(channel.code, 404)
411380 self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
412381
424393 {"actions": ["dont_notify"]},
425394 access_token=token,
426395 )
427 self.render(request)
428396 self.assertEqual(channel.code, 404)
429397 self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
430398
442410 {"actions": ["dont_notify"]},
443411 access_token=token,
444412 )
445 self.render(request)
446 self.assertEqual(channel.code, 404)
447 self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
413 self.assertEqual(channel.code, 404)
414 self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
8585 request, channel = self.make_request(
8686 "PUT", self.created_rmid_msg_path, b'{"msgtype":"m.text","body":"test msg"}'
8787 )
88 self.render(request)
8988 self.assertEquals(200, channel.code, channel.result)
9089
9190 # set topic for public room
9493 ("rooms/%s/state/m.room.topic" % self.created_public_rmid).encode("ascii"),
9594 b'{"topic":"Public Room Topic"}',
9695 )
97 self.render(request)
9896 self.assertEquals(200, channel.code, channel.result)
9997
10098 # auth as user_id now
117115 "/rooms/%s/send/m.room.message/mid2" % (self.uncreated_rmid,),
118116 msg_content,
119117 )
120 self.render(request)
121118 self.assertEquals(403, channel.code, msg=channel.result["body"])
122119
123120 # send message in created room not joined (no state), expect 403
124121 request, channel = self.make_request("PUT", send_msg_path(), msg_content)
125 self.render(request)
126122 self.assertEquals(403, channel.code, msg=channel.result["body"])
127123
128124 # send message in created room and invited, expect 403
130126 room=self.created_rmid, src=self.rmcreator_id, targ=self.user_id
131127 )
132128 request, channel = self.make_request("PUT", send_msg_path(), msg_content)
133 self.render(request)
134129 self.assertEquals(403, channel.code, msg=channel.result["body"])
135130
136131 # send message in created room and joined, expect 200
137132 self.helper.join(room=self.created_rmid, user=self.user_id)
138133 request, channel = self.make_request("PUT", send_msg_path(), msg_content)
139 self.render(request)
140134 self.assertEquals(200, channel.code, msg=channel.result["body"])
141135
142136 # send message in created room and left, expect 403
143137 self.helper.leave(room=self.created_rmid, user=self.user_id)
144138 request, channel = self.make_request("PUT", send_msg_path(), msg_content)
145 self.render(request)
146139 self.assertEquals(403, channel.code, msg=channel.result["body"])
147140
148141 def test_topic_perms(self):
153146 request, channel = self.make_request(
154147 "PUT", "/rooms/%s/state/m.room.topic" % self.uncreated_rmid, topic_content
155148 )
156 self.render(request)
157149 self.assertEquals(403, channel.code, msg=channel.result["body"])
158150 request, channel = self.make_request(
159151 "GET", "/rooms/%s/state/m.room.topic" % self.uncreated_rmid
160152 )
161 self.render(request)
162153 self.assertEquals(403, channel.code, msg=channel.result["body"])
163154
164155 # set/get topic in created PRIVATE room not joined, expect 403
165156 request, channel = self.make_request("PUT", topic_path, topic_content)
166 self.render(request)
167157 self.assertEquals(403, channel.code, msg=channel.result["body"])
168158 request, channel = self.make_request("GET", topic_path)
169 self.render(request)
170159 self.assertEquals(403, channel.code, msg=channel.result["body"])
171160
172161 # set topic in created PRIVATE room and invited, expect 403
174163 room=self.created_rmid, src=self.rmcreator_id, targ=self.user_id
175164 )
176165 request, channel = self.make_request("PUT", topic_path, topic_content)
177 self.render(request)
178166 self.assertEquals(403, channel.code, msg=channel.result["body"])
179167
180168 # get topic in created PRIVATE room and invited, expect 403
181169 request, channel = self.make_request("GET", topic_path)
182 self.render(request)
183170 self.assertEquals(403, channel.code, msg=channel.result["body"])
184171
185172 # set/get topic in created PRIVATE room and joined, expect 200
188175 # Only room ops can set topic by default
189176 self.helper.auth_user_id = self.rmcreator_id
190177 request, channel = self.make_request("PUT", topic_path, topic_content)
191 self.render(request)
192178 self.assertEquals(200, channel.code, msg=channel.result["body"])
193179 self.helper.auth_user_id = self.user_id
194180
195181 request, channel = self.make_request("GET", topic_path)
196 self.render(request)
197182 self.assertEquals(200, channel.code, msg=channel.result["body"])
198183 self.assert_dict(json.loads(topic_content.decode("utf8")), channel.json_body)
199184
200185 # set/get topic in created PRIVATE room and left, expect 403
201186 self.helper.leave(room=self.created_rmid, user=self.user_id)
202187 request, channel = self.make_request("PUT", topic_path, topic_content)
203 self.render(request)
204188 self.assertEquals(403, channel.code, msg=channel.result["body"])
205189 request, channel = self.make_request("GET", topic_path)
206 self.render(request)
207190 self.assertEquals(200, channel.code, msg=channel.result["body"])
208191
209192 # get topic in PUBLIC room, not joined, expect 403
210193 request, channel = self.make_request(
211194 "GET", "/rooms/%s/state/m.room.topic" % self.created_public_rmid
212195 )
213 self.render(request)
214196 self.assertEquals(403, channel.code, msg=channel.result["body"])
215197
216198 # set topic in PUBLIC room, not joined, expect 403
219201 "/rooms/%s/state/m.room.topic" % self.created_public_rmid,
220202 topic_content,
221203 )
222 self.render(request)
223204 self.assertEquals(403, channel.code, msg=channel.result["body"])
224205
225206 def _test_get_membership(self, room=None, members=[], expect_code=None):
226207 for member in members:
227208 path = "/rooms/%s/state/m.room.member/%s" % (room, member)
228209 request, channel = self.make_request("GET", path)
229 self.render(request)
230210 self.assertEquals(expect_code, channel.code)
231211
232212 def test_membership_basic_room_perms(self):
399379 def test_get_member_list(self):
400380 room_id = self.helper.create_room_as(self.user_id)
401381 request, channel = self.make_request("GET", "/rooms/%s/members" % room_id)
402 self.render(request)
403382 self.assertEquals(200, channel.code, msg=channel.result["body"])
404383
405384 def test_get_member_list_no_room(self):
406385 request, channel = self.make_request("GET", "/rooms/roomdoesnotexist/members")
407 self.render(request)
408386 self.assertEquals(403, channel.code, msg=channel.result["body"])
409387
410388 def test_get_member_list_no_permission(self):
411389 room_id = self.helper.create_room_as("@some_other_guy:red")
412390 request, channel = self.make_request("GET", "/rooms/%s/members" % room_id)
413 self.render(request)
414391 self.assertEquals(403, channel.code, msg=channel.result["body"])
415392
416393 def test_get_member_list_mixed_memberships(self):
420397 self.helper.invite(room=room_id, src=room_creator, targ=self.user_id)
421398 # can't see list if you're just invited.
422399 request, channel = self.make_request("GET", room_path)
423 self.render(request)
424400 self.assertEquals(403, channel.code, msg=channel.result["body"])
425401
426402 self.helper.join(room=room_id, user=self.user_id)
427403 # can see list now joined
428404 request, channel = self.make_request("GET", room_path)
429 self.render(request)
430405 self.assertEquals(200, channel.code, msg=channel.result["body"])
431406
432407 self.helper.leave(room=room_id, user=self.user_id)
433408 # can see old list once left
434409 request, channel = self.make_request("GET", room_path)
435 self.render(request)
436410 self.assertEquals(200, channel.code, msg=channel.result["body"])
437411
438412
445419 # POST with no config keys, expect new room id
446420 request, channel = self.make_request("POST", "/createRoom", "{}")
447421
448 self.render(request)
449422 self.assertEquals(200, channel.code, channel.result)
450423 self.assertTrue("room_id" in channel.json_body)
451424
454427 request, channel = self.make_request(
455428 "POST", "/createRoom", b'{"visibility":"private"}'
456429 )
457 self.render(request)
458430 self.assertEquals(200, channel.code)
459431 self.assertTrue("room_id" in channel.json_body)
460432
463435 request, channel = self.make_request(
464436 "POST", "/createRoom", b'{"custom":"stuff"}'
465437 )
466 self.render(request)
467438 self.assertEquals(200, channel.code)
468439 self.assertTrue("room_id" in channel.json_body)
469440
472443 request, channel = self.make_request(
473444 "POST", "/createRoom", b'{"visibility":"private","custom":"things"}'
474445 )
475 self.render(request)
476446 self.assertEquals(200, channel.code)
477447 self.assertTrue("room_id" in channel.json_body)
478448
479449 def test_post_room_invalid_content(self):
480450 # POST with invalid content / paths, expect 400
481451 request, channel = self.make_request("POST", "/createRoom", b'{"visibili')
482 self.render(request)
483452 self.assertEquals(400, channel.code)
484453
485454 request, channel = self.make_request("POST", "/createRoom", b'["hello"]')
486 self.render(request)
487455 self.assertEquals(400, channel.code)
488456
489457 def test_post_room_invitees_invalid_mxid(self):
492460 request, channel = self.make_request(
493461 "POST", "/createRoom", b'{"invite":["@alice:example.com "]}'
494462 )
495 self.render(request)
496463 self.assertEquals(400, channel.code)
497464
498465
509476 def test_invalid_puts(self):
510477 # missing keys or invalid json
511478 request, channel = self.make_request("PUT", self.path, "{}")
512 self.render(request)
513479 self.assertEquals(400, channel.code, msg=channel.result["body"])
514480
515481 request, channel = self.make_request("PUT", self.path, '{"_name":"bo"}')
516 self.render(request)
517482 self.assertEquals(400, channel.code, msg=channel.result["body"])
518483
519484 request, channel = self.make_request("PUT", self.path, '{"nao')
520 self.render(request)
521485 self.assertEquals(400, channel.code, msg=channel.result["body"])
522486
523487 request, channel = self.make_request(
524488 "PUT", self.path, '[{"_name":"bo"},{"_name":"jill"}]'
525489 )
526 self.render(request)
527490 self.assertEquals(400, channel.code, msg=channel.result["body"])
528491
529492 request, channel = self.make_request("PUT", self.path, "text only")
530 self.render(request)
531493 self.assertEquals(400, channel.code, msg=channel.result["body"])
532494
533495 request, channel = self.make_request("PUT", self.path, "")
534 self.render(request)
535496 self.assertEquals(400, channel.code, msg=channel.result["body"])
536497
537498 # valid key, wrong type
538499 content = '{"topic":["Topic name"]}'
539500 request, channel = self.make_request("PUT", self.path, content)
540 self.render(request)
541501 self.assertEquals(400, channel.code, msg=channel.result["body"])
542502
543503 def test_rooms_topic(self):
544504 # nothing should be there
545505 request, channel = self.make_request("GET", self.path)
546 self.render(request)
547506 self.assertEquals(404, channel.code, msg=channel.result["body"])
548507
549508 # valid put
550509 content = '{"topic":"Topic name"}'
551510 request, channel = self.make_request("PUT", self.path, content)
552 self.render(request)
553511 self.assertEquals(200, channel.code, msg=channel.result["body"])
554512
555513 # valid get
556514 request, channel = self.make_request("GET", self.path)
557 self.render(request)
558515 self.assertEquals(200, channel.code, msg=channel.result["body"])
559516 self.assert_dict(json.loads(content), channel.json_body)
560517
562519 # valid put with extra keys
563520 content = '{"topic":"Seasons","subtopic":"Summer"}'
564521 request, channel = self.make_request("PUT", self.path, content)
565 self.render(request)
566522 self.assertEquals(200, channel.code, msg=channel.result["body"])
567523
568524 # valid get
569525 request, channel = self.make_request("GET", self.path)
570 self.render(request)
571526 self.assertEquals(200, channel.code, msg=channel.result["body"])
572527 self.assert_dict(json.loads(content), channel.json_body)
573528
584539 path = "/rooms/%s/state/m.room.member/%s" % (self.room_id, self.user_id)
585540 # missing keys or invalid json
586541 request, channel = self.make_request("PUT", path, "{}")
587 self.render(request)
588542 self.assertEquals(400, channel.code, msg=channel.result["body"])
589543
590544 request, channel = self.make_request("PUT", path, '{"_name":"bo"}')
591 self.render(request)
592545 self.assertEquals(400, channel.code, msg=channel.result["body"])
593546
594547 request, channel = self.make_request("PUT", path, '{"nao')
595 self.render(request)
596548 self.assertEquals(400, channel.code, msg=channel.result["body"])
597549
598550 request, channel = self.make_request(
599551 "PUT", path, b'[{"_name":"bo"},{"_name":"jill"}]'
600552 )
601 self.render(request)
602553 self.assertEquals(400, channel.code, msg=channel.result["body"])
603554
604555 request, channel = self.make_request("PUT", path, "text only")
605 self.render(request)
606556 self.assertEquals(400, channel.code, msg=channel.result["body"])
607557
608558 request, channel = self.make_request("PUT", path, "")
609 self.render(request)
610559 self.assertEquals(400, channel.code, msg=channel.result["body"])
611560
612561 # valid keys, wrong types
616565 Membership.LEAVE,
617566 )
618567 request, channel = self.make_request("PUT", path, content.encode("ascii"))
619 self.render(request)
620568 self.assertEquals(400, channel.code, msg=channel.result["body"])
621569
622570 def test_rooms_members_self(self):
628576 # valid join message (NOOP since we made the room)
629577 content = '{"membership":"%s"}' % Membership.JOIN
630578 request, channel = self.make_request("PUT", path, content.encode("ascii"))
631 self.render(request)
632579 self.assertEquals(200, channel.code, msg=channel.result["body"])
633580
634581 request, channel = self.make_request("GET", path, None)
635 self.render(request)
636582 self.assertEquals(200, channel.code, msg=channel.result["body"])
637583
638584 expected_response = {"membership": Membership.JOIN}
648594 # valid invite message
649595 content = '{"membership":"%s"}' % Membership.INVITE
650596 request, channel = self.make_request("PUT", path, content)
651 self.render(request)
652597 self.assertEquals(200, channel.code, msg=channel.result["body"])
653598
654599 request, channel = self.make_request("GET", path, None)
655 self.render(request)
656600 self.assertEquals(200, channel.code, msg=channel.result["body"])
657601 self.assertEquals(json.loads(content), channel.json_body)
658602
669613 "Join us!",
670614 )
671615 request, channel = self.make_request("PUT", path, content)
672 self.render(request)
673616 self.assertEquals(200, channel.code, msg=channel.result["body"])
674617
675618 request, channel = self.make_request("GET", path, None)
676 self.render(request)
677619 self.assertEquals(200, channel.code, msg=channel.result["body"])
678620 self.assertEquals(json.loads(content), channel.json_body)
679621
724666 # Update the display name for the user.
725667 path = "/_matrix/client/r0/profile/%s/displayname" % self.user_id
726668 request, channel = self.make_request("PUT", path, {"displayname": "John Doe"})
727 self.render(request)
728669 self.assertEquals(channel.code, 200, channel.json_body)
729670
730671 # Check that all the rooms have been sent a profile update into.
735676 )
736677
737678 request, channel = self.make_request("GET", path)
738 self.render(request)
739679 self.assertEquals(channel.code, 200)
740680
741681 self.assertIn("displayname", channel.json_body)
760700 # if all of these requests ended up joining the user to a room.
761701 for i in range(4):
762702 request, channel = self.make_request("POST", path % room_id, {})
763 self.render(request)
764703 self.assertEquals(channel.code, 200)
765704
766705
776715 path = "/rooms/%s/send/m.room.message/mid1" % (urlparse.quote(self.room_id))
777716 # missing keys or invalid json
778717 request, channel = self.make_request("PUT", path, b"{}")
779 self.render(request)
780718 self.assertEquals(400, channel.code, msg=channel.result["body"])
781719
782720 request, channel = self.make_request("PUT", path, b'{"_name":"bo"}')
783 self.render(request)
784721 self.assertEquals(400, channel.code, msg=channel.result["body"])
785722
786723 request, channel = self.make_request("PUT", path, b'{"nao')
787 self.render(request)
788724 self.assertEquals(400, channel.code, msg=channel.result["body"])
789725
790726 request, channel = self.make_request(
791727 "PUT", path, b'[{"_name":"bo"},{"_name":"jill"}]'
792728 )
793 self.render(request)
794729 self.assertEquals(400, channel.code, msg=channel.result["body"])
795730
796731 request, channel = self.make_request("PUT", path, b"text only")
797 self.render(request)
798732 self.assertEquals(400, channel.code, msg=channel.result["body"])
799733
800734 request, channel = self.make_request("PUT", path, b"")
801 self.render(request)
802735 self.assertEquals(400, channel.code, msg=channel.result["body"])
803736
804737 def test_rooms_messages_sent(self):
806739
807740 content = b'{"body":"test","msgtype":{"type":"a"}}'
808741 request, channel = self.make_request("PUT", path, content)
809 self.render(request)
810742 self.assertEquals(400, channel.code, msg=channel.result["body"])
811743
812744 # custom message types
813745 content = b'{"body":"test","msgtype":"test.custom.text"}'
814746 request, channel = self.make_request("PUT", path, content)
815 self.render(request)
816747 self.assertEquals(200, channel.code, msg=channel.result["body"])
817748
818749 # m.text message type
819750 path = "/rooms/%s/send/m.room.message/mid2" % (urlparse.quote(self.room_id))
820751 content = b'{"body":"test2","msgtype":"m.text"}'
821752 request, channel = self.make_request("PUT", path, content)
822 self.render(request)
823753 self.assertEquals(200, channel.code, msg=channel.result["body"])
824754
825755
836766 request, channel = self.make_request(
837767 "GET", "/rooms/%s/initialSync" % self.room_id
838768 )
839 self.render(request)
840769 self.assertEquals(200, channel.code)
841770
842771 self.assertEquals(self.room_id, channel.json_body["room_id"])
880809 request, channel = self.make_request(
881810 "GET", "/rooms/%s/messages?access_token=x&from=%s" % (self.room_id, token)
882811 )
883 self.render(request)
884812 self.assertEquals(200, channel.code)
885813 self.assertTrue("start" in channel.json_body)
886814 self.assertEquals(token, channel.json_body["start"])
892820 request, channel = self.make_request(
893821 "GET", "/rooms/%s/messages?access_token=x&from=%s" % (self.room_id, token)
894822 )
895 self.render(request)
896823 self.assertEquals(200, channel.code)
897824 self.assertTrue("start" in channel.json_body)
898825 self.assertEquals(token, channel.json_body["start"])
932859 json.dumps({"types": [EventTypes.Message]}),
933860 ),
934861 )
935 self.render(request)
936862 self.assertEqual(channel.code, 200, channel.json_body)
937863
938864 chunk = channel.json_body["chunk"]
961887 json.dumps({"types": [EventTypes.Message]}),
962888 ),
963889 )
964 self.render(request)
965890 self.assertEqual(channel.code, 200, channel.json_body)
966891
967892 chunk = channel.json_body["chunk"]
979904 json.dumps({"types": [EventTypes.Message]}),
980905 ),
981906 )
982 self.render(request)
983907 self.assertEqual(channel.code, 200, channel.json_body)
984908
985909 chunk = channel.json_body["chunk"]
1039963 }
1040964 },
1041965 )
1042 self.render(request)
1043966
1044967 # Check we get the results we expect -- one search result, of the sent
1045968 # messages
1073996 }
1074997 },
1075998 )
1076 self.render(request)
1077999
10781000 # Check we get the results we expect -- one search result, of the sent
10791001 # messages
11101032
11111033 def test_restricted_no_auth(self):
11121034 request, channel = self.make_request("GET", self.url)
1113 self.render(request)
11141035 self.assertEqual(channel.code, 401, channel.result)
11151036
11161037 def test_restricted_auth(self):
11181039 tok = self.login("user", "pass")
11191040
11201041 request, channel = self.make_request("GET", self.url, access_token=tok)
1121 self.render(request)
11221042 self.assertEqual(channel.code, 200, channel.result)
11231043
11241044
11521072 request_data,
11531073 access_token=self.tok,
11541074 )
1155 self.render(request)
11561075 self.assertEqual(channel.code, 200, channel.result)
11571076
11581077 self.room_id = self.helper.create_room_as(self.user_id, tok=self.tok)
11671086 request_data,
11681087 access_token=self.tok,
11691088 )
1170 self.render(request)
11711089 self.assertEqual(channel.code, 200, channel.result)
11721090 event_id = channel.json_body["event_id"]
11731091
11761094 "/_matrix/client/r0/rooms/%s/event/%s" % (self.room_id, event_id),
11771095 access_token=self.tok,
11781096 )
1179 self.render(request)
11801097 self.assertEqual(channel.code, 200, channel.result)
11811098
11821099 res_displayname = channel.json_body["content"]["displayname"]
12111128 content={"reason": reason},
12121129 access_token=self.second_tok,
12131130 )
1214 self.render(request)
12151131 self.assertEqual(channel.code, 200, channel.result)
12161132
12171133 self._check_for_reason(reason)
12261142 content={"reason": reason},
12271143 access_token=self.second_tok,
12281144 )
1229 self.render(request)
12301145 self.assertEqual(channel.code, 200, channel.result)
12311146
12321147 self._check_for_reason(reason)
12411156 content={"reason": reason, "user_id": self.second_user_id},
12421157 access_token=self.second_tok,
12431158 )
1244 self.render(request)
12451159 self.assertEqual(channel.code, 200, channel.result)
12461160
12471161 self._check_for_reason(reason)
12561170 content={"reason": reason, "user_id": self.second_user_id},
12571171 access_token=self.creator_tok,
12581172 )
1259 self.render(request)
12601173 self.assertEqual(channel.code, 200, channel.result)
12611174
12621175 self._check_for_reason(reason)
12691182 content={"reason": reason, "user_id": self.second_user_id},
12701183 access_token=self.creator_tok,
12711184 )
1272 self.render(request)
12731185 self.assertEqual(channel.code, 200, channel.result)
12741186
12751187 self._check_for_reason(reason)
12821194 content={"reason": reason, "user_id": self.second_user_id},
12831195 access_token=self.creator_tok,
12841196 )
1285 self.render(request)
12861197 self.assertEqual(channel.code, 200, channel.result)
12871198
12881199 self._check_for_reason(reason)
13021213 content={"reason": reason},
13031214 access_token=self.second_tok,
13041215 )
1305 self.render(request)
13061216 self.assertEqual(channel.code, 200, channel.result)
13071217
13081218 self._check_for_reason(reason)
13151225 ),
13161226 access_token=self.creator_tok,
13171227 )
1318 self.render(request)
13191228 self.assertEqual(channel.code, 200, channel.result)
13201229
13211230 event_content = channel.json_body
13641273 % (self.room_id, event_id, json.dumps(self.FILTER_LABELS)),
13651274 access_token=self.tok,
13661275 )
1367 self.render(request)
13681276 self.assertEqual(channel.code, 200, channel.result)
13691277
13701278 events_before = channel.json_body["events_before"]
13951303 % (self.room_id, event_id, json.dumps(self.FILTER_NOT_LABELS)),
13961304 access_token=self.tok,
13971305 )
1398 self.render(request)
13991306 self.assertEqual(channel.code, 200, channel.result)
14001307
14011308 events_before = channel.json_body["events_before"]
14311338 % (self.room_id, event_id, json.dumps(self.FILTER_LABELS_NOT_LABELS)),
14321339 access_token=self.tok,
14331340 )
1434 self.render(request)
14351341 self.assertEqual(channel.code, 200, channel.result)
14361342
14371343 events_before = channel.json_body["events_before"]
14591365 "/rooms/%s/messages?access_token=%s&from=%s&filter=%s"
14601366 % (self.room_id, self.tok, token, json.dumps(self.FILTER_LABELS)),
14611367 )
1462 self.render(request)
14631368
14641369 events = channel.json_body["chunk"]
14651370
14771382 "/rooms/%s/messages?access_token=%s&from=%s&filter=%s"
14781383 % (self.room_id, self.tok, token, json.dumps(self.FILTER_NOT_LABELS)),
14791384 )
1480 self.render(request)
14811385
14821386 events = channel.json_body["chunk"]
14831387
15061410 json.dumps(self.FILTER_LABELS_NOT_LABELS),
15071411 ),
15081412 )
1509 self.render(request)
15101413
15111414 events = channel.json_body["chunk"]
15121415
15311434 request, channel = self.make_request(
15321435 "POST", "/search?access_token=%s" % self.tok, request_data
15331436 )
1534 self.render(request)
15351437
15361438 results = channel.json_body["search_categories"]["room_events"]["results"]
15371439
15671469 request, channel = self.make_request(
15681470 "POST", "/search?access_token=%s" % self.tok, request_data
15691471 )
1570 self.render(request)
15711472
15721473 results = channel.json_body["search_categories"]["room_events"]["results"]
15731474
16151516 request, channel = self.make_request(
16161517 "POST", "/search?access_token=%s" % self.tok, request_data
16171518 )
1618 self.render(request)
16191519
16201520 results = channel.json_body["search_categories"]["room_events"]["results"]
16211521
17401640 % (self.room_id, event_id),
17411641 access_token=self.tok,
17421642 )
1743 self.render(request)
17441643 self.assertEqual(channel.code, 200, channel.result)
17451644
17461645 events_before = channel.json_body["events_before"]
18051704 % (self.room_id, event_id),
18061705 access_token=invited_tok,
18071706 )
1808 self.render(request)
18091707 self.assertEqual(channel.code, 200, channel.result)
18101708
18111709 events_before = channel.json_body["events_before"]
18961794 % (self.room_id,),
18971795 access_token=access_token,
18981796 )
1899 self.render(request)
19001797 self.assertEqual(channel.code, expected_code, channel.result)
19011798 res = channel.json_body
19021799 self.assertIsInstance(res, dict)
19151812 request, channel = self.make_request(
19161813 "PUT", url, request_data, access_token=self.room_owner_tok
19171814 )
1918 self.render(request)
19191815 self.assertEqual(channel.code, expected_code, channel.result)
19201816
19211817
19461842 request, channel = self.make_request(
19471843 "PUT", url, request_data, access_token=self.room_owner_tok
19481844 )
1949 self.render(request)
19501845 self.assertEqual(channel.code, expected_code, channel.result)
19511846
19521847 def _get_canonical_alias(self, expected_code: int = 200) -> JsonDict:
19561851 "rooms/%s/state/m.room.canonical_alias" % (self.room_id,),
19571852 access_token=self.room_owner_tok,
19581853 )
1959 self.render(request)
19601854 self.assertEqual(channel.code, expected_code, channel.result)
19611855 res = channel.json_body
19621856 self.assertIsInstance(res, dict)
19701864 json.dumps(content),
19711865 access_token=self.room_owner_tok,
19721866 )
1973 self.render(request)
19741867 self.assertEqual(channel.code, expected_code, channel.result)
19751868 res = channel.json_body
19761869 self.assertIsInstance(res, dict)
9898 "/rooms/%s/typing/%s" % (self.room_id, self.user_id),
9999 b'{"typing": true, "timeout": 30000}',
100100 )
101 self.render(request)
102101 self.assertEquals(200, channel.code)
103102
104103 self.assertEquals(self.event_source.get_current_key(), 1)
122121 "/rooms/%s/typing/%s" % (self.room_id, self.user_id),
123122 b'{"typing": false}',
124123 )
125 self.render(request)
126124 self.assertEquals(200, channel.code)
127125
128126 def test_typing_timeout(self):
131129 "/rooms/%s/typing/%s" % (self.room_id, self.user_id),
132130 b'{"typing": true, "timeout": 30000}',
133131 )
134 self.render(request)
135132 self.assertEquals(200, channel.code)
136133
137134 self.assertEquals(self.event_source.get_current_key(), 1)
145142 "/rooms/%s/typing/%s" % (self.room_id, self.user_id),
146143 b'{"typing": true, "timeout": 30000}',
147144 )
148 self.render(request)
149145 self.assertEquals(200, channel.code)
150146
151147 self.assertEquals(self.event_source.get_current_key(), 3)
2222 import attr
2323
2424 from twisted.web.resource import Resource
25 from twisted.web.server import Site
2526
2627 from synapse.api.constants import Membership
2728
28 from tests.server import make_request, render
29 from tests.server import FakeSite, make_request
2930
3031
3132 @attr.s
3536 """
3637
3738 hs = attr.ib()
38 resource = attr.ib()
39 site = attr.ib(type=Site)
3940 auth_user_id = attr.ib()
4041
4142 def create_room_as(
42 self, room_creator=None, is_public=True, tok=None, expect_code=200,
43 ):
43 self,
44 room_creator: str = None,
45 is_public: bool = True,
46 room_version: str = None,
47 tok: str = None,
48 expect_code: int = 200,
49 ) -> str:
50 """
51 Create a room.
52
53 Args:
54 room_creator: The user ID to create the room with.
55 is_public: If True, the `visibility` parameter will be set to the
56 default (public). Otherwise, the `visibility` parameter will be set
57 to "private".
58 room_version: The room version to create the room as. Defaults to Synapse's
59 default room version.
60 tok: The access token to use in the request.
61 expect_code: The expected HTTP response code.
62
63 Returns:
64 The ID of the newly created room.
65 """
4466 temp_id = self.auth_user_id
4567 self.auth_user_id = room_creator
4668 path = "/_matrix/client/r0/createRoom"
4769 content = {}
4870 if not is_public:
4971 content["visibility"] = "private"
72 if room_version:
73 content["room_version"] = room_version
5074 if tok:
5175 path = path + "?access_token=%s" % tok
5276
53 request, channel = make_request(
54 self.hs.get_reactor(), "POST", path, json.dumps(content).encode("utf8")
55 )
56 render(request, self.resource, self.hs.get_reactor())
77 _, channel = make_request(
78 self.hs.get_reactor(),
79 self.site,
80 "POST",
81 path,
82 json.dumps(content).encode("utf8"),
83 )
5784
5885 assert channel.result["code"] == b"%d" % expect_code, channel.result
5986 self.auth_user_id = temp_id
123150 data = {"membership": membership}
124151 data.update(extra_data)
125152
126 request, channel = make_request(
127 self.hs.get_reactor(), "PUT", path, json.dumps(data).encode("utf8")
128 )
129
130 render(request, self.resource, self.hs.get_reactor())
153 _, channel = make_request(
154 self.hs.get_reactor(),
155 self.site,
156 "PUT",
157 path,
158 json.dumps(data).encode("utf8"),
159 )
131160
132161 assert int(channel.result["code"]) == expect_code, (
133162 "Expected: %d, got: %d, resp: %r"
156185 if tok:
157186 path = path + "?access_token=%s" % tok
158187
159 request, channel = make_request(
160 self.hs.get_reactor(), "PUT", path, json.dumps(content).encode("utf8")
161 )
162 render(request, self.resource, self.hs.get_reactor())
188 _, channel = make_request(
189 self.hs.get_reactor(),
190 self.site,
191 "PUT",
192 path,
193 json.dumps(content).encode("utf8"),
194 )
163195
164196 assert int(channel.result["code"]) == expect_code, (
165197 "Expected: %d, got: %d, resp: %r"
209241 if body is not None:
210242 content = json.dumps(body).encode("utf8")
211243
212 request, channel = make_request(self.hs.get_reactor(), method, path, content)
213
214 render(request, self.resource, self.hs.get_reactor())
244 _, channel = make_request(
245 self.hs.get_reactor(), self.site, method, path, content
246 )
215247
216248 assert int(channel.result["code"]) == expect_code, (
217249 "Expected: %d, got: %d, resp: %r"
294326 """
295327 image_length = len(image_data)
296328 path = "/_matrix/media/r0/upload?filename=%s" % (filename,)
297 request, channel = make_request(
298 self.hs.get_reactor(), "POST", path, content=image_data, access_token=tok
299 )
300 request.requestHeaders.addRawHeader(
301 b"Content-Length", str(image_length).encode("UTF-8")
302 )
303 request.render(resource)
304 self.hs.get_reactor().pump([100])
329 _, channel = make_request(
330 self.hs.get_reactor(),
331 FakeSite(resource),
332 "POST",
333 path,
334 content=image_data,
335 access_token=tok,
336 custom_headers=[(b"Content-Length", str(image_length))],
337 )
305338
306339 assert channel.code == expect_code, "Expected: %d, got: %d, resp: %r" % (
307340 expect_code,
3030 from synapse.rest.synapse.client.password_reset import PasswordResetSubmitTokenResource
3131
3232 from tests import unittest
33 from tests.server import FakeSite, make_request
3334 from tests.unittest import override_config
3435
3536
244245 b"account/password/email/requestToken",
245246 {"client_secret": client_secret, "email": email, "send_attempt": 1},
246247 )
247 self.render(request)
248248 self.assertEquals(200, channel.code, channel.result)
249249
250250 return channel.json_body["sid"]
254254 path = link.replace("https://example.com", "")
255255
256256 # Load the password reset confirmation page
257 request, channel = self.make_request("GET", path, shorthand=False)
258 request.render(self.submit_token_resource)
259 self.pump()
257 request, channel = make_request(
258 self.reactor,
259 FakeSite(self.submit_token_resource),
260 "GET",
261 path,
262 shorthand=False,
263 )
264
260265 self.assertEquals(200, channel.code, channel.result)
261266
262267 # Now POST to the same endpoint, mimicking the same behaviour as clicking the
270275 form_args.append(arg)
271276
272277 # Confirm the password reset
273 request, channel = self.make_request(
278 request, channel = make_request(
279 self.reactor,
280 FakeSite(self.submit_token_resource),
274281 "POST",
275282 path,
276283 content=urlencode(form_args).encode("utf8"),
277284 shorthand=False,
278285 content_is_form=True,
279286 )
280 request.render(self.submit_token_resource)
281 self.pump()
282287 self.assertEquals(200, channel.code, channel.result)
283288
284289 def _get_link_from_email(self):
318323 },
319324 },
320325 )
321 self.render(request)
322326 self.assertEquals(expected_code, channel.code, channel.result)
323327
324328
348352
349353 # Check that this access token has been invalidated.
350354 request, channel = self.make_request("GET", "account/whoami")
351 self.render(request)
352355 self.assertEqual(request.code, 401)
353356
354357 def test_pending_invites(self):
406409 request, channel = self.make_request(
407410 "POST", "account/deactivate", request_data, access_token=tok
408411 )
409 self.render(request)
410412 self.assertEqual(request.code, 200)
411413
412414
541543 },
542544 access_token=self.user_id_tok,
543545 )
544 self.render(request)
545546 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
546547 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
547548
549550 request, channel = self.make_request(
550551 "GET", self.url_3pid, access_token=self.user_id_tok,
551552 )
552 self.render(request)
553553
554554 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
555555 self.assertFalse(channel.json_body["threepids"])
574574 {"medium": "email", "address": self.email},
575575 access_token=self.user_id_tok,
576576 )
577 self.render(request)
578577 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
579578
580579 # Get user
581580 request, channel = self.make_request(
582581 "GET", self.url_3pid, access_token=self.user_id_tok,
583582 )
584 self.render(request)
585583
586584 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
587585 self.assertFalse(channel.json_body["threepids"])
608606 {"medium": "email", "address": self.email},
609607 access_token=self.user_id_tok,
610608 )
611 self.render(request)
612609
613610 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
614611 self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
617614 request, channel = self.make_request(
618615 "GET", self.url_3pid, access_token=self.user_id_tok,
619616 )
620 self.render(request)
621617
622618 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
623619 self.assertEqual("email", channel.json_body["threepids"][0]["medium"])
646642 },
647643 access_token=self.user_id_tok,
648644 )
649 self.render(request)
650645 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
651646 self.assertEqual(Codes.THREEPID_AUTH_FAILED, channel.json_body["errcode"])
652647
654649 request, channel = self.make_request(
655650 "GET", self.url_3pid, access_token=self.user_id_tok,
656651 )
657 self.render(request)
658652
659653 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
660654 self.assertFalse(channel.json_body["threepids"])
681675 },
682676 access_token=self.user_id_tok,
683677 )
684 self.render(request)
685678 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
686679 self.assertEqual(Codes.THREEPID_AUTH_FAILED, channel.json_body["errcode"])
687680
689682 request, channel = self.make_request(
690683 "GET", self.url_3pid, access_token=self.user_id_tok,
691684 )
692 self.render(request)
693685
694686 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
695687 self.assertFalse(channel.json_body["threepids"])
794786 request, channel = self.make_request(
795787 "POST", b"account/3pid/email/requestToken", body,
796788 )
797 self.render(request)
798789 self.assertEquals(expect_code, channel.code, channel.result)
799790
800791 return channel.json_body.get("sid")
807798 b"account/3pid/email/requestToken",
808799 {"client_secret": client_secret, "email": email, "send_attempt": 1},
809800 )
810 self.render(request)
811801 self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
812802 self.assertEqual(expected_errcode, channel.json_body["errcode"])
813803 self.assertEqual(expected_error, channel.json_body["error"])
817807 path = link.replace("https://example.com", "")
818808
819809 request, channel = self.make_request("GET", path, shorthand=False)
820 self.render(request)
821810 self.assertEquals(200, channel.code, channel.result)
822811
823812 def _get_link_from_email(self):
866855 access_token=self.user_id_tok,
867856 )
868857
869 self.render(request)
870858 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
871859
872860 # Get user
873861 request, channel = self.make_request(
874862 "GET", self.url_3pid, access_token=self.user_id_tok,
875863 )
876 self.render(request)
877864
878865 self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
879866 self.assertEqual("email", channel.json_body["threepids"][0]["medium"])
3737 return succeed(True)
3838
3939
40 class DummyPasswordChecker(UserInteractiveAuthChecker):
41 def check_auth(self, authdict, clientip):
42 return succeed(authdict["identifier"]["user"])
43
44
4540 class FallbackAuthTests(unittest.HomeserverTestCase):
4641
4742 servlets = [
7166 request, channel = self.make_request(
7267 "POST", "register", body
7368 ) # type: SynapseRequest, FakeChannel
74 self.render(request)
7569
7670 self.assertEqual(request.code, expected_response)
7771 return channel
8680 request, channel = self.make_request(
8781 "GET", "auth/m.login.recaptcha/fallback/web?session=" + session
8882 ) # type: SynapseRequest, FakeChannel
89 self.render(request)
9083 self.assertEqual(request.code, 200)
9184
9285 request, channel = self.make_request(
9588 + post_session
9689 + "&g-recaptcha-response=a",
9790 )
98 self.render(request)
9991 self.assertEqual(request.code, expected_post_response)
10092
10193 # The recaptcha handler is called with the response given
164156 ]
165157
166158 def prepare(self, reactor, clock, hs):
167 auth_handler = hs.get_auth_handler()
168 auth_handler.checkers[LoginType.PASSWORD] = DummyPasswordChecker(hs)
169
170159 self.user_pass = "pass"
171160 self.user = self.register_user("test", self.user_pass)
172161 self.user_tok = self.login("test", self.user_pass)
176165 request, channel = self.make_request(
177166 "GET", "devices", access_token=self.user_tok,
178167 ) # type: SynapseRequest, FakeChannel
179 self.render(request)
180168
181169 # Get the ID of the device.
182170 self.assertEqual(request.code, 200)
189177 request, channel = self.make_request(
190178 "DELETE", "devices/" + device, body, access_token=self.user_tok
191179 ) # type: SynapseRequest, FakeChannel
192 self.render(request)
193180
194181 # Ensure the response is sane.
195182 self.assertEqual(request.code, expected_response)
203190 request, channel = self.make_request(
204191 "POST", "delete_devices", body, access_token=self.user_tok,
205192 ) # type: SynapseRequest, FakeChannel
206 self.render(request)
207193
208194 # Ensure the response is sane.
209195 self.assertEqual(request.code, expected_response)
233219 "auth": {
234220 "type": "m.login.password",
235221 "identifier": {"type": "m.id.user", "user": self.user},
222 "password": self.user_pass,
223 "session": session,
224 },
225 },
226 )
227
228 def test_grandfathered_identifier(self):
229 """Check behaviour without "identifier" dict
230
231 Synapse used to require clients to submit a "user" field for m.login.password
232 UIA - check that still works.
233 """
234
235 device_id = self.get_device_ids()[0]
236 channel = self.delete_device(device_id, 401)
237 session = channel.json_body["session"]
238
239 # Make another request providing the UI auth flow.
240 self.delete_device(
241 device_id,
242 200,
243 {
244 "auth": {
245 "type": "m.login.password",
246 "user": self.user,
236247 "password": self.user_pass,
237248 "session": session,
238249 },
3636
3737 def test_check_auth_required(self):
3838 request, channel = self.make_request("GET", self.url)
39 self.render(request)
4039
4140 self.assertEqual(channel.code, 401)
4241
4544 access_token = self.login("user", "pass")
4645
4746 request, channel = self.make_request("GET", self.url, access_token=access_token)
48 self.render(request)
4947 capabilities = channel.json_body["capabilities"]
5048
5149 self.assertEqual(channel.code, 200)
6462 access_token = self.login(user, password)
6563
6664 request, channel = self.make_request("GET", self.url, access_token=access_token)
67 self.render(request)
6865 capabilities = channel.json_body["capabilities"]
6966
7067 self.assertEqual(channel.code, 200)
7370 self.assertTrue(capabilities["m.change_password"]["enabled"])
7471 self.get_success(self.store.user_set_password_hash(user, None))
7572 request, channel = self.make_request("GET", self.url, access_token=access_token)
76 self.render(request)
7773 capabilities = channel.json_body["capabilities"]
7874
7975 self.assertEqual(channel.code, 200)
4040 "/_matrix/client/r0/user/%s/filter" % (self.user_id),
4141 self.EXAMPLE_FILTER_JSON,
4242 )
43 self.render(request)
4443
4544 self.assertEqual(channel.result["code"], b"200")
4645 self.assertEqual(channel.json_body, {"filter_id": "0"})
5453 "/_matrix/client/r0/user/%s/filter" % ("@watermelon:test"),
5554 self.EXAMPLE_FILTER_JSON,
5655 )
57 self.render(request)
5856
5957 self.assertEqual(channel.result["code"], b"403")
6058 self.assertEquals(channel.json_body["errcode"], Codes.FORBIDDEN)
6765 "/_matrix/client/r0/user/%s/filter" % (self.user_id),
6866 self.EXAMPLE_FILTER_JSON,
6967 )
70 self.render(request)
7168
7269 self.hs.is_mine = _is_mine
7370 self.assertEqual(channel.result["code"], b"403")
8481 request, channel = self.make_request(
8582 "GET", "/_matrix/client/r0/user/%s/filter/%s" % (self.user_id, filter_id)
8683 )
87 self.render(request)
8884
8985 self.assertEqual(channel.result["code"], b"200")
9086 self.assertEquals(channel.json_body, self.EXAMPLE_FILTER)
9389 request, channel = self.make_request(
9490 "GET", "/_matrix/client/r0/user/%s/filter/12382148321" % (self.user_id)
9591 )
96 self.render(request)
9792
9893 self.assertEqual(channel.result["code"], b"404")
9994 self.assertEquals(channel.json_body["errcode"], Codes.NOT_FOUND)
10499 request, channel = self.make_request(
105100 "GET", "/_matrix/client/r0/user/%s/filter/foobar" % (self.user_id)
106101 )
107 self.render(request)
108102
109103 self.assertEqual(channel.result["code"], b"400")
110104
113107 request, channel = self.make_request(
114108 "GET", "/_matrix/client/r0/user/%s/filter/" % (self.user_id)
115109 )
116 self.render(request)
117110
118111 self.assertEqual(channel.result["code"], b"400")
7272 request, channel = self.make_request(
7373 "GET", "/_matrix/client/r0/password_policy"
7474 )
75 self.render(request)
7675
7776 self.assertEqual(channel.code, 200, channel.result)
7877 self.assertEqual(
9089 def test_password_too_short(self):
9190 request_data = json.dumps({"username": "kermit", "password": "shorty"})
9291 request, channel = self.make_request("POST", self.register_url, request_data)
93 self.render(request)
9492
9593 self.assertEqual(channel.code, 400, channel.result)
9694 self.assertEqual(
10098 def test_password_no_digit(self):
10199 request_data = json.dumps({"username": "kermit", "password": "longerpassword"})
102100 request, channel = self.make_request("POST", self.register_url, request_data)
103 self.render(request)
104101
105102 self.assertEqual(channel.code, 400, channel.result)
106103 self.assertEqual(
110107 def test_password_no_symbol(self):
111108 request_data = json.dumps({"username": "kermit", "password": "l0ngerpassword"})
112109 request, channel = self.make_request("POST", self.register_url, request_data)
113 self.render(request)
114110
115111 self.assertEqual(channel.code, 400, channel.result)
116112 self.assertEqual(
120116 def test_password_no_uppercase(self):
121117 request_data = json.dumps({"username": "kermit", "password": "l0ngerpassword!"})
122118 request, channel = self.make_request("POST", self.register_url, request_data)
123 self.render(request)
124119
125120 self.assertEqual(channel.code, 400, channel.result)
126121 self.assertEqual(
130125 def test_password_no_lowercase(self):
131126 request_data = json.dumps({"username": "kermit", "password": "L0NGERPASSWORD!"})
132127 request, channel = self.make_request("POST", self.register_url, request_data)
133 self.render(request)
134128
135129 self.assertEqual(channel.code, 400, channel.result)
136130 self.assertEqual(
140134 def test_password_compliant(self):
141135 request_data = json.dumps({"username": "kermit", "password": "L0ngerpassword!"})
142136 request, channel = self.make_request("POST", self.register_url, request_data)
143 self.render(request)
144137
145138 # Getting a 401 here means the password has passed validation and the server has
146139 # responded with a list of registration flows.
172165 request_data,
173166 access_token=tok,
174167 )
175 self.render(request)
176168
177169 self.assertEqual(channel.code, 400, channel.result)
178170 self.assertEqual(channel.json_body["errcode"], Codes.PASSWORD_NO_DIGIT)
6363 request, channel = self.make_request(
6464 b"POST", self.url + b"?access_token=i_am_an_app_service", request_data
6565 )
66 self.render(request)
6766
6867 self.assertEquals(channel.result["code"], b"200", channel.result)
6968 det_data = {"user_id": user_id, "home_server": self.hs.hostname}
7574 request, channel = self.make_request(
7675 b"POST", self.url + b"?access_token=i_am_an_app_service", request_data
7776 )
78 self.render(request)
7977
8078 self.assertEquals(channel.result["code"], b"401", channel.result)
8179
8280 def test_POST_bad_password(self):
8381 request_data = json.dumps({"username": "kermit", "password": 666})
8482 request, channel = self.make_request(b"POST", self.url, request_data)
85 self.render(request)
8683
8784 self.assertEquals(channel.result["code"], b"400", channel.result)
8885 self.assertEquals(channel.json_body["error"], "Invalid password")
9087 def test_POST_bad_username(self):
9188 request_data = json.dumps({"username": 777, "password": "monkey"})
9289 request, channel = self.make_request(b"POST", self.url, request_data)
93 self.render(request)
9490
9591 self.assertEquals(channel.result["code"], b"400", channel.result)
9692 self.assertEquals(channel.json_body["error"], "Invalid username")
106102 }
107103 request_data = json.dumps(params)
108104 request, channel = self.make_request(b"POST", self.url, request_data)
109 self.render(request)
110105
111106 det_data = {
112107 "user_id": user_id,
122117 self.auth_result = (None, {"username": "kermit", "password": "monkey"}, None)
123118
124119 request, channel = self.make_request(b"POST", self.url, request_data)
125 self.render(request)
126120
127121 self.assertEquals(channel.result["code"], b"403", channel.result)
128122 self.assertEquals(channel.json_body["error"], "Registration has been disabled")
132126 self.hs.config.allow_guest_access = True
133127
134128 request, channel = self.make_request(b"POST", self.url + b"?kind=guest", b"{}")
135 self.render(request)
136129
137130 det_data = {"home_server": self.hs.hostname, "device_id": "guest_device"}
138131 self.assertEquals(channel.result["code"], b"200", channel.result)
142135 self.hs.config.allow_guest_access = False
143136
144137 request, channel = self.make_request(b"POST", self.url + b"?kind=guest", b"{}")
145 self.render(request)
146138
147139 self.assertEquals(channel.result["code"], b"403", channel.result)
148140 self.assertEquals(channel.json_body["error"], "Guest access is disabled")
152144 for i in range(0, 6):
153145 url = self.url + b"?kind=guest"
154146 request, channel = self.make_request(b"POST", url, b"{}")
155 self.render(request)
156147
157148 if i == 5:
158149 self.assertEquals(channel.result["code"], b"429", channel.result)
163154 self.reactor.advance(retry_after_ms / 1000.0 + 1.0)
164155
165156 request, channel = self.make_request(b"POST", self.url + b"?kind=guest", b"{}")
166 self.render(request)
167157
168158 self.assertEquals(channel.result["code"], b"200", channel.result)
169159
178168 }
179169 request_data = json.dumps(params)
180170 request, channel = self.make_request(b"POST", self.url, request_data)
181 self.render(request)
182171
183172 if i == 5:
184173 self.assertEquals(channel.result["code"], b"429", channel.result)
189178 self.reactor.advance(retry_after_ms / 1000.0 + 1.0)
190179
191180 request, channel = self.make_request(b"POST", self.url + b"?kind=guest", b"{}")
192 self.render(request)
193181
194182 self.assertEquals(channel.result["code"], b"200", channel.result)
195183
196184 def test_advertised_flows(self):
197185 request, channel = self.make_request(b"POST", self.url, b"{}")
198 self.render(request)
199186 self.assertEquals(channel.result["code"], b"401", channel.result)
200187 flows = channel.json_body["flows"]
201188
219206 )
220207 def test_advertised_flows_captcha_and_terms_and_3pids(self):
221208 request, channel = self.make_request(b"POST", self.url, b"{}")
222 self.render(request)
223209 self.assertEquals(channel.result["code"], b"401", channel.result)
224210 flows = channel.json_body["flows"]
225211
252238 )
253239 def test_advertised_flows_no_msisdn_email_required(self):
254240 request, channel = self.make_request(b"POST", self.url, b"{}")
255 self.render(request)
256241 self.assertEquals(channel.result["code"], b"401", channel.result)
257242 flows = channel.json_body["flows"]
258243
297282 b"register/email/requestToken",
298283 {"client_secret": "foobar", "email": email, "send_attempt": 1},
299284 )
300 self.render(request)
301285 self.assertEquals(200, channel.code, channel.result)
302286
303287 self.assertIsNotNone(channel.json_body.get("sid"))
333317 # The specific endpoint doesn't matter, all we need is an authenticated
334318 # endpoint.
335319 request, channel = self.make_request(b"GET", "/sync", access_token=tok)
336 self.render(request)
337320
338321 self.assertEquals(channel.result["code"], b"200", channel.result)
339322
340323 self.reactor.advance(datetime.timedelta(weeks=1).total_seconds())
341324
342325 request, channel = self.make_request(b"GET", "/sync", access_token=tok)
343 self.render(request)
344326
345327 self.assertEquals(channel.result["code"], b"403", channel.result)
346328 self.assertEquals(
359341 self.register_user("admin", "adminpassword", admin=True)
360342 admin_tok = self.login("admin", "adminpassword")
361343
362 url = "/_matrix/client/unstable/admin/account_validity/validity"
344 url = "/_synapse/admin/v1/account_validity/validity"
363345 params = {"user_id": user_id}
364346 request_data = json.dumps(params)
365347 request, channel = self.make_request(
366348 b"POST", url, request_data, access_token=admin_tok
367349 )
368 self.render(request)
369350 self.assertEquals(channel.result["code"], b"200", channel.result)
370351
371352 # The specific endpoint doesn't matter, all we need is an authenticated
372353 # endpoint.
373354 request, channel = self.make_request(b"GET", "/sync", access_token=tok)
374 self.render(request)
375355 self.assertEquals(channel.result["code"], b"200", channel.result)
376356
377357 def test_manual_expire(self):
381361 self.register_user("admin", "adminpassword", admin=True)
382362 admin_tok = self.login("admin", "adminpassword")
383363
384 url = "/_matrix/client/unstable/admin/account_validity/validity"
364 url = "/_synapse/admin/v1/account_validity/validity"
385365 params = {
386366 "user_id": user_id,
387367 "expiration_ts": 0,
391371 request, channel = self.make_request(
392372 b"POST", url, request_data, access_token=admin_tok
393373 )
394 self.render(request)
395374 self.assertEquals(channel.result["code"], b"200", channel.result)
396375
397376 # The specific endpoint doesn't matter, all we need is an authenticated
398377 # endpoint.
399378 request, channel = self.make_request(b"GET", "/sync", access_token=tok)
400 self.render(request)
401379 self.assertEquals(channel.result["code"], b"403", channel.result)
402380 self.assertEquals(
403381 channel.json_body["errcode"], Codes.EXPIRED_ACCOUNT, channel.result
410388 self.register_user("admin", "adminpassword", admin=True)
411389 admin_tok = self.login("admin", "adminpassword")
412390
413 url = "/_matrix/client/unstable/admin/account_validity/validity"
391 url = "/_synapse/admin/v1/account_validity/validity"
414392 params = {
415393 "user_id": user_id,
416394 "expiration_ts": 0,
420398 request, channel = self.make_request(
421399 b"POST", url, request_data, access_token=admin_tok
422400 )
423 self.render(request)
424401 self.assertEquals(channel.result["code"], b"200", channel.result)
425402
426403 # Try to log the user out
427404 request, channel = self.make_request(b"POST", "/logout", access_token=tok)
428 self.render(request)
429405 self.assertEquals(channel.result["code"], b"200", channel.result)
430406
431407 # Log the user in again (allowed for expired accounts)
433409
434410 # Try to log out all of the user's sessions
435411 request, channel = self.make_request(b"POST", "/logout/all", access_token=tok)
436 self.render(request)
437412 self.assertEquals(channel.result["code"], b"200", channel.result)
438413
439414
508483 renewal_token = self.get_success(self.store.get_renewal_token_for_user(user_id))
509484 url = "/_matrix/client/unstable/account_validity/renew?token=%s" % renewal_token
510485 request, channel = self.make_request(b"GET", url)
511 self.render(request)
512486 self.assertEquals(channel.result["code"], b"200", channel.result)
513487
514488 # Check that we're getting HTML back.
529503 # succeed.
530504 self.reactor.advance(datetime.timedelta(days=3).total_seconds())
531505 request, channel = self.make_request(b"GET", "/sync", access_token=tok)
532 self.render(request)
533506 self.assertEquals(channel.result["code"], b"200", channel.result)
534507
535508 def test_renewal_invalid_token(self):
537510 # expected, i.e. that it responds with 404 Not Found and the correct HTML.
538511 url = "/_matrix/client/unstable/account_validity/renew?token=123"
539512 request, channel = self.make_request(b"GET", url)
540 self.render(request)
541513 self.assertEquals(channel.result["code"], b"404", channel.result)
542514
543515 # Check that we're getting HTML back.
563535 "/_matrix/client/unstable/account_validity/send_mail",
564536 access_token=tok,
565537 )
566 self.render(request)
567538 self.assertEquals(channel.result["code"], b"200", channel.result)
568539
569540 self.assertEqual(len(self.email_attempts), 1)
586557 request, channel = self.make_request(
587558 "POST", "account/deactivate", request_data, access_token=tok
588559 )
589 self.render(request)
590560 self.assertEqual(request.code, 200)
591561
592562 self.reactor.advance(datetime.timedelta(days=8).total_seconds())
598568 tok = self.login("kermit", "monkey")
599569 # We need to manually add an email address otherwise the handler will do
600570 # nothing.
601 now = self.hs.clock.time_msec()
571 now = self.hs.get_clock().time_msec()
602572 self.get_success(
603573 self.store.user_add_threepid(
604574 user_id=user_id,
616586
617587 # We need to manually add an email address otherwise the handler will do
618588 # nothing.
619 now = self.hs.clock.time_msec()
589 now = self.hs.get_clock().time_msec()
620590 self.get_success(
621591 self.store.user_add_threepid(
622592 user_id=user_id,
640610 "/_matrix/client/unstable/account_validity/send_mail",
641611 access_token=tok,
642612 )
643 self.render(request)
644613 self.assertEquals(channel.result["code"], b"200", channel.result)
645614
646615 self.assertEqual(len(self.email_attempts), 1)
676645
677646 self.hs.config.account_validity.startup_job_max_delta = self.max_delta
678647
679 now_ms = self.hs.clock.time_msec()
648 now_ms = self.hs.get_clock().time_msec()
680649 self.get_success(self.store._set_expiration_date_when_missing())
681650
682651 res = self.get_success(self.store.get_expiration_ts_for_user(user_id))
6464 "/rooms/%s/event/%s" % (self.room, event_id),
6565 access_token=self.user_token,
6666 )
67 self.render(request)
6867 self.assertEquals(200, channel.code, channel.json_body)
6968
7069 self.assert_dict(
113112 % (self.room, self.parent_id),
114113 access_token=self.user_token,
115114 )
116 self.render(request)
117115 self.assertEquals(200, channel.code, channel.json_body)
118116
119117 # We expect to get back a single pagination result, which is the full
159157 % (self.room, self.parent_id, from_token),
160158 access_token=self.user_token,
161159 )
162 self.render(request)
163160 self.assertEquals(200, channel.code, channel.json_body)
164161
165162 found_event_ids.extend(e["event_id"] for e in channel.json_body["chunk"])
218215 % (self.room, self.parent_id, from_token),
219216 access_token=self.user_token,
220217 )
221 self.render(request)
222218 self.assertEquals(200, channel.code, channel.json_body)
223219
224220 self.assertEqual(len(channel.json_body["chunk"]), 1, channel.json_body)
295291 ),
296292 access_token=self.user_token,
297293 )
298 self.render(request)
299294 self.assertEquals(200, channel.code, channel.json_body)
300295
301296 self.assertEqual(len(channel.json_body["chunk"]), 1, channel.json_body)
335330 % (self.room, self.parent_id),
336331 access_token=self.user_token,
337332 )
338 self.render(request)
339333 self.assertEquals(200, channel.code, channel.json_body)
340334
341335 self.assertEquals(
368362 access_token=self.user_token,
369363 content={},
370364 )
371 self.render(request)
372365 self.assertEquals(200, channel.code, channel.json_body)
373366
374367 request, channel = self.make_request(
377370 % (self.room, self.parent_id),
378371 access_token=self.user_token,
379372 )
380 self.render(request)
381373 self.assertEquals(200, channel.code, channel.json_body)
382374
383375 self.assertEquals(
395387 % (self.room, self.parent_id, RelationTypes.REPLACE),
396388 access_token=self.user_token,
397389 )
398 self.render(request)
399390 self.assertEquals(400, channel.code, channel.json_body)
400391
401392 def test_aggregation_get_event(self):
427418 "/rooms/%s/event/%s" % (self.room, self.parent_id),
428419 access_token=self.user_token,
429420 )
430 self.render(request)
431421 self.assertEquals(200, channel.code, channel.json_body)
432422
433423 self.assertEquals(
464454 "/rooms/%s/event/%s" % (self.room, self.parent_id),
465455 access_token=self.user_token,
466456 )
467 self.render(request)
468457 self.assertEquals(200, channel.code, channel.json_body)
469458
470459 self.assertEquals(channel.json_body["content"], new_body)
522511 "/rooms/%s/event/%s" % (self.room, self.parent_id),
523512 access_token=self.user_token,
524513 )
525 self.render(request)
526514 self.assertEquals(200, channel.code, channel.json_body)
527515
528516 self.assertEquals(channel.json_body["content"], new_body)
566554 % (self.room, original_event_id),
567555 access_token=self.user_token,
568556 )
569 self.render(request)
570557 self.assertEquals(200, channel.code, channel.json_body)
571558
572559 self.assertIn("chunk", channel.json_body)
580567 access_token=self.user_token,
581568 content="{}",
582569 )
583 self.render(request)
584570 self.assertEquals(200, channel.code, channel.json_body)
585571
586572 # Try to check for remaining m.replace relations
590576 % (self.room, original_event_id),
591577 access_token=self.user_token,
592578 )
593 self.render(request)
594579 self.assertEquals(200, channel.code, channel.json_body)
595580
596581 # Check that no relations are returned
623608 access_token=self.user_token,
624609 content="{}",
625610 )
626 self.render(request)
627611 self.assertEquals(200, channel.code, channel.json_body)
628612
629613 # Check that aggregations returns zero
633617 % (self.room, original_event_id),
634618 access_token=self.user_token,
635619 )
636 self.render(request)
637620 self.assertEquals(200, channel.code, channel.json_body)
638621
639622 self.assertIn("chunk", channel.json_body)
679662 json.dumps(content).encode("utf-8"),
680663 access_token=access_token,
681664 )
682 self.render(request)
683665 return channel
684666
685667 def _create_user(self, localpart):
4646 % other_user,
4747 access_token=token,
4848 )
49 self.render(request)
5049 return request, channel
5150
5251 def test_shared_room_list_public(self):
3535
3636 def test_sync_argless(self):
3737 request, channel = self.make_request("GET", "/sync")
38 self.render(request)
3938
4039 self.assertEqual(channel.code, 200)
4140 self.assertTrue(
5655 self.hs.config.use_presence = False
5756
5857 request, channel = self.make_request("GET", "/sync")
59 self.render(request)
6058
6159 self.assertEqual(channel.code, 200)
6260 self.assertTrue(
198196 request, channel = self.make_request(
199197 "GET", "/sync?filter=%s" % sync_filter, access_token=tok
200198 )
201 self.render(request)
202199 self.assertEqual(channel.code, 200, channel.result)
203200
204201 return channel.json_body["rooms"]["join"][room_id]["timeline"]["events"]
252249 typing_url % (room, other_user_id, other_access_token),
253250 b'{"typing": true, "timeout": 30000}',
254251 )
255 self.render(request)
256252 self.assertEquals(200, channel.code)
257253
258254 request, channel = self.make_request(
259255 "GET", "/sync?access_token=%s" % (access_token,)
260256 )
261 self.render(request)
262257 self.assertEquals(200, channel.code)
263258 next_batch = channel.json_body["next_batch"]
264259
268263 typing_url % (room, other_user_id, other_access_token),
269264 b'{"typing": false}',
270265 )
271 self.render(request)
272266 self.assertEquals(200, channel.code)
273267
274268 # Start typing.
277271 typing_url % (room, other_user_id, other_access_token),
278272 b'{"typing": true, "timeout": 30000}',
279273 )
280 self.render(request)
281274 self.assertEquals(200, channel.code)
282275
283276 # Should return immediately
284277 request, channel = self.make_request(
285278 "GET", sync_url % (access_token, next_batch)
286279 )
287 self.render(request)
288280 self.assertEquals(200, channel.code)
289281 next_batch = channel.json_body["next_batch"]
290282
299291 request, channel = self.make_request(
300292 "GET", sync_url % (access_token, next_batch)
301293 )
302 self.render(request)
303294 self.assertEquals(200, channel.code)
304295 next_batch = channel.json_body["next_batch"]
305296
310301 request, channel = self.make_request(
311302 "GET", sync_url % (access_token, next_batch)
312303 )
313 self.render(request)
314304 self.assertEquals(200, channel.code)
315305 next_batch = channel.json_body["next_batch"]
316306
319309 typing._reset()
320310
321311 # Now it SHOULD fail as it never completes!
322 request, channel = self.make_request(
323 "GET", sync_url % (access_token, next_batch)
324 )
325 self.assertRaises(TimedOutException, self.render, request)
312 with self.assertRaises(TimedOutException):
313 self.make_request("GET", sync_url % (access_token, next_batch))
326314
327315
328316 class UnreadMessagesTestCase(unittest.HomeserverTestCase):
400388 body,
401389 access_token=self.tok,
402390 )
403 self.render(request)
404391 self.assertEqual(channel.code, 200, channel.json_body)
405392
406393 # Check that the unread counter is back to 0.
465452 request, channel = self.make_request(
466453 "GET", self.url % self.next_batch, access_token=self.tok,
467454 )
468 self.render(request)
469455
470456 self.assertEqual(channel.code, 200, channel.json_body)
471457
3131 from synapse.util.stringutils import random_string
3232
3333 from tests import unittest
34 from tests.server import FakeChannel, wait_until_result
34 from tests.server import FakeChannel
3535 from tests.utils import default_config
3636
3737
4040 self.http_client = Mock()
4141 return self.setup_test_homeserver(http_client=self.http_client)
4242
43 def create_test_json_resource(self):
43 def create_test_resource(self):
4444 return create_resource_tree(
4545 {"/_matrix/key/v2": KeyApiV2Resource(self.hs)}, root_resource=NoResource()
4646 )
9393 % (server_name.encode("utf-8"), key_id.encode("utf-8")),
9494 b"1.1",
9595 )
96 wait_until_result(self.reactor, req)
96 channel.await_result()
9797 self.assertEqual(channel.code, 200)
9898 resp = channel.json_body
9999 return resp
189189 req.requestReceived(
190190 b"POST", path.encode("utf-8"), b"1.1",
191191 )
192 wait_until_result(self.reactor, req)
192 channel.await_result()
193193 self.assertEqual(channel.code, 200)
194194 resp = channel.json_body
195195 return resp
3535 from synapse.rest.media.v1.storage_provider import FileStorageProviderBackend
3636
3737 from tests import unittest
38 from tests.server import FakeSite, make_request
3839
3940
4041 class MediaStorageTests(unittest.HomeserverTestCase):
226227
227228 def _req(self, content_disposition):
228229
229 request, channel = self.make_request("GET", self.media_id, shorthand=False)
230 request.render(self.download_resource)
230 request, channel = make_request(
231 self.reactor,
232 FakeSite(self.download_resource),
233 "GET",
234 self.media_id,
235 shorthand=False,
236 await_result=False,
237 )
231238 self.pump()
232239
233240 # We've made one fetch, to example.com, using the media URL, and asking
316323
317324 def _test_thumbnail(self, method, expected_body, expected_found):
318325 params = "?width=32&height=32&method=" + method
319 request, channel = self.make_request(
320 "GET", self.media_id + params, shorthand=False
321 )
322 request.render(self.thumbnail_resource)
326 request, channel = make_request(
327 self.reactor,
328 FakeSite(self.thumbnail_resource),
329 "GET",
330 self.media_id + params,
331 shorthand=False,
332 await_result=False,
333 )
323334 self.pump()
324335
325336 headers = {
347358 channel.json_body,
348359 {
349360 "errcode": "M_NOT_FOUND",
350 "error": "Not found [b'example.com', b'12345?width=32&height=32&method=%s']"
351 % method,
361 "error": "Not found [b'example.com', b'12345']",
352362 },
353363 )
132132
133133 self.reactor.nameResolver = Resolver()
134134
135 def create_test_resource(self):
136 return self.hs.get_media_repository_resource()
137
135138 def test_cache_returns_correct_type(self):
136139 self.lookups["matrix.org"] = [(IPv4Address, "10.1.2.3")]
137140
138141 request, channel = self.make_request(
139 "GET", "url_preview?url=http://matrix.org", shorthand=False
140 )
141 request.render(self.preview_url)
142 "GET",
143 "preview_url?url=http://matrix.org",
144 shorthand=False,
145 await_result=False,
146 )
142147 self.pump()
143148
144149 client = self.reactor.tcpClients[0][2].buildProtocol(None)
159164
160165 # Check the cache returns the correct response
161166 request, channel = self.make_request(
162 "GET", "url_preview?url=http://matrix.org", shorthand=False
163 )
164 request.render(self.preview_url)
165 self.pump()
167 "GET", "preview_url?url=http://matrix.org", shorthand=False
168 )
166169
167170 # Check the cache response has the same content
168171 self.assertEqual(channel.code, 200)
177180
178181 # Check the database cache returns the correct response
179182 request, channel = self.make_request(
180 "GET", "url_preview?url=http://matrix.org", shorthand=False
181 )
182 request.render(self.preview_url)
183 self.pump()
183 "GET", "preview_url?url=http://matrix.org", shorthand=False
184 )
184185
185186 # Check the cache response has the same content
186187 self.assertEqual(channel.code, 200)
200201 )
201202
202203 request, channel = self.make_request(
203 "GET", "url_preview?url=http://matrix.org", shorthand=False
204 )
205 request.render(self.preview_url)
204 "GET",
205 "preview_url?url=http://matrix.org",
206 shorthand=False,
207 await_result=False,
208 )
206209 self.pump()
207210
208211 client = self.reactor.tcpClients[0][2].buildProtocol(None)
233236 )
234237
235238 request, channel = self.make_request(
236 "GET", "url_preview?url=http://matrix.org", shorthand=False
237 )
238 request.render(self.preview_url)
239 "GET",
240 "preview_url?url=http://matrix.org",
241 shorthand=False,
242 await_result=False,
243 )
239244 self.pump()
240245
241246 client = self.reactor.tcpClients[0][2].buildProtocol(None)
266271 )
267272
268273 request, channel = self.make_request(
269 "GET", "url_preview?url=http://matrix.org", shorthand=False
270 )
271 request.render(self.preview_url)
274 "GET",
275 "preview_url?url=http://matrix.org",
276 shorthand=False,
277 await_result=False,
278 )
272279 self.pump()
273280
274281 client = self.reactor.tcpClients[0][2].buildProtocol(None)
297304 self.lookups["example.com"] = [(IPv4Address, "10.1.2.3")]
298305
299306 request, channel = self.make_request(
300 "GET", "url_preview?url=http://example.com", shorthand=False
301 )
302 request.render(self.preview_url)
307 "GET",
308 "preview_url?url=http://example.com",
309 shorthand=False,
310 await_result=False,
311 )
303312 self.pump()
304313
305314 client = self.reactor.tcpClients[0][2].buildProtocol(None)
325334 self.lookups["example.com"] = [(IPv4Address, "192.168.1.1")]
326335
327336 request, channel = self.make_request(
328 "GET", "url_preview?url=http://example.com", shorthand=False
329 )
330 request.render(self.preview_url)
331 self.pump()
337 "GET", "preview_url?url=http://example.com", shorthand=False
338 )
332339
333340 # No requests made.
334341 self.assertEqual(len(self.reactor.tcpClients), 0)
348355 self.lookups["example.com"] = [(IPv4Address, "1.1.1.2")]
349356
350357 request, channel = self.make_request(
351 "GET", "url_preview?url=http://example.com", shorthand=False
352 )
353 request.render(self.preview_url)
354 self.pump()
358 "GET", "preview_url?url=http://example.com", shorthand=False
359 )
355360
356361 self.assertEqual(channel.code, 502)
357362 self.assertEqual(
367372 Blacklisted IP addresses, accessed directly, are not spidered.
368373 """
369374 request, channel = self.make_request(
370 "GET", "url_preview?url=http://192.168.1.1", shorthand=False
371 )
372 request.render(self.preview_url)
373 self.pump()
375 "GET", "preview_url?url=http://192.168.1.1", shorthand=False
376 )
374377
375378 # No requests made.
376379 self.assertEqual(len(self.reactor.tcpClients), 0)
388391 Blacklisted IP ranges, accessed directly, are not spidered.
389392 """
390393 request, channel = self.make_request(
391 "GET", "url_preview?url=http://1.1.1.2", shorthand=False
392 )
393 request.render(self.preview_url)
394 self.pump()
394 "GET", "preview_url?url=http://1.1.1.2", shorthand=False
395 )
395396
396397 self.assertEqual(channel.code, 403)
397398 self.assertEqual(
410411 self.lookups["example.com"] = [(IPv4Address, "1.1.1.1")]
411412
412413 request, channel = self.make_request(
413 "GET", "url_preview?url=http://example.com", shorthand=False
414 )
415 request.render(self.preview_url)
414 "GET",
415 "preview_url?url=http://example.com",
416 shorthand=False,
417 await_result=False,
418 )
416419 self.pump()
417420
418421 client = self.reactor.tcpClients[0][2].buildProtocol(None)
445448 ]
446449
447450 request, channel = self.make_request(
448 "GET", "url_preview?url=http://example.com", shorthand=False
449 )
450 request.render(self.preview_url)
451 self.pump()
451 "GET", "preview_url?url=http://example.com", shorthand=False
452 )
452453 self.assertEqual(channel.code, 502)
453454 self.assertEqual(
454455 channel.json_body,
467468 ]
468469
469470 request, channel = self.make_request(
470 "GET", "url_preview?url=http://example.com", shorthand=False
471 )
472 request.render(self.preview_url)
473 self.pump()
471 "GET", "preview_url?url=http://example.com", shorthand=False
472 )
474473
475474 # No requests made.
476475 self.assertEqual(len(self.reactor.tcpClients), 0)
490489 self.lookups["example.com"] = [(IPv6Address, "2001:800::1")]
491490
492491 request, channel = self.make_request(
493 "GET", "url_preview?url=http://example.com", shorthand=False
494 )
495 request.render(self.preview_url)
496 self.pump()
492 "GET", "preview_url?url=http://example.com", shorthand=False
493 )
497494
498495 self.assertEqual(channel.code, 502)
499496 self.assertEqual(
509506 OPTIONS returns the OPTIONS.
510507 """
511508 request, channel = self.make_request(
512 "OPTIONS", "url_preview?url=http://example.com", shorthand=False
513 )
514 request.render(self.preview_url)
515 self.pump()
509 "OPTIONS", "preview_url?url=http://example.com", shorthand=False
510 )
516511 self.assertEqual(channel.code, 200)
517512 self.assertEqual(channel.json_body, {})
518513
524519
525520 # Build and make a request to the server
526521 request, channel = self.make_request(
527 "GET", "url_preview?url=http://example.com", shorthand=False
528 )
529 request.render(self.preview_url)
522 "GET",
523 "preview_url?url=http://example.com",
524 shorthand=False,
525 await_result=False,
526 )
530527 self.pump()
531528
532529 # Extract Synapse's tcp client
597594
598595 request, channel = self.make_request(
599596 "GET",
600 "url_preview?url=http://twitter.com/matrixdotorg/status/12345",
597 "preview_url?url=http://twitter.com/matrixdotorg/status/12345",
601598 shorthand=False,
602 )
603 request.render(self.preview_url)
599 await_result=False,
600 )
604601 self.pump()
605602
606603 client = self.reactor.tcpClients[0][2].buildProtocol(None)
662659
663660 request, channel = self.make_request(
664661 "GET",
665 "url_preview?url=http://twitter.com/matrixdotorg/status/12345",
662 "preview_url?url=http://twitter.com/matrixdotorg/status/12345",
666663 shorthand=False,
667 )
668 request.render(self.preview_url)
664 await_result=False,
665 )
669666 self.pump()
670667
671668 client = self.reactor.tcpClients[0][2].buildProtocol(None)
1919
2020
2121 class HealthCheckTests(unittest.HomeserverTestCase):
22 def setUp(self):
23 super().setUp()
24
22 def create_test_resource(self):
2523 # replace the JsonResource with a HealthResource.
26 self.resource = HealthResource()
24 return HealthResource()
2725
2826 def test_health(self):
2927 request, channel = self.make_request("GET", "/health", shorthand=False)
30 self.render(request)
3128
3229 self.assertEqual(request.code, 200)
3330 self.assertEqual(channel.result["body"], b"OK")
1919
2020
2121 class WellKnownTests(unittest.HomeserverTestCase):
22 def setUp(self):
23 super().setUp()
24
22 def create_test_resource(self):
2523 # replace the JsonResource with a WellKnownResource
26 self.resource = WellKnownResource(self.hs)
24 return WellKnownResource(self.hs)
2725
2826 def test_well_known(self):
2927 self.hs.config.public_baseurl = "https://tesths"
3230 request, channel = self.make_request(
3331 "GET", "/.well-known/matrix/client", shorthand=False
3432 )
35 self.render(request)
3633
3734 self.assertEqual(request.code, 200)
3835 self.assertEqual(
4946 request, channel = self.make_request(
5047 "GET", "/.well-known/matrix/client", shorthand=False
5148 )
52 self.render(request)
5349
5450 self.assertEqual(request.code, 404)
11 import logging
22 from collections import deque
33 from io import SEEK_END, BytesIO
4 from typing import Callable
4 from typing import Callable, Iterable, Optional, Tuple, Union
55
66 import attr
77 from typing_extensions import Deque
1818 )
1919 from twisted.python.failure import Failure
2020 from twisted.test.proto_helpers import AccumulatingProtocol, MemoryReactorClock
21 from twisted.web.http import unquote
2221 from twisted.web.http_headers import Headers
22 from twisted.web.resource import IResource
2323 from twisted.web.server import Site
2424
2525 from synapse.http.site import SynapseRequest
116116 def transport(self):
117117 return self
118118
119 def await_result(self, timeout: int = 100) -> None:
120 """
121 Wait until the request is finished.
122 """
123 self._reactor.run()
124 x = 0
125
126 while not self.result.get("done"):
127 # If there's a producer, tell it to resume producing so we get content
128 if self._producer:
129 self._producer.resumeProducing()
130
131 x += 1
132
133 if x > timeout:
134 raise TimedOutException("Timed out waiting for request to finish.")
135
136 self._reactor.advance(0.1)
137
119138
120139 class FakeSite:
121140 """
127146 site_tag = "test"
128147 access_logger = logging.getLogger("synapse.access.http.fake")
129148
149 def __init__(self, resource: IResource):
150 """
151
152 Args:
153 resource: the resource to be used for rendering all requests
154 """
155 self._resource = resource
156
157 def getResourceFor(self, request):
158 return self._resource
159
130160
131161 def make_request(
132162 reactor,
163 site: Site,
133164 method,
134165 path,
135166 content=b"",
138169 shorthand=True,
139170 federation_auth_origin=None,
140171 content_is_form=False,
172 await_result: bool = True,
173 custom_headers: Optional[
174 Iterable[Tuple[Union[bytes, str], Union[bytes, str]]]
175 ] = None,
141176 ):
142177 """
143 Make a web request using the given method and path, feed it the
144 content, and return the Request and the Channel underneath.
178 Make a web request using the given method, path and content, and render it
179
180 Returns the Request and the Channel underneath.
145181
146182 Args:
183 site: The twisted Site to use to render the request
184
147185 method (bytes/unicode): The HTTP request method ("verb").
148186 path (bytes/unicode): The HTTP path, suitably URL encoded (e.g.
149187 escaped UTF-8 & spaces and such).
156194 content_is_form: Whether the content is URL encoded form data. Adds the
157195 'Content-Type': 'application/x-www-form-urlencoded' header.
158196
197 custom_headers: (name, value) pairs to add as request headers
198
199 await_result: whether to wait for the request to complete rendering. If true,
200 will pump the reactor until the the renderer tells the channel the request
201 is finished.
202
159203 Returns:
160204 Tuple[synapse.http.site.SynapseRequest, channel]
161205 """
177221 if not path.startswith(b"/"):
178222 path = b"/" + path
179223
224 if isinstance(content, dict):
225 content = json.dumps(content).encode("utf8")
180226 if isinstance(content, str):
181227 content = content.encode("utf8")
182228
183 site = FakeSite()
184229 channel = FakeChannel(site, reactor)
185230
186231 req = request(channel)
187 req.process = lambda: b""
188232 req.content = BytesIO(content)
189233 # Twisted expects to be at the end of the content when parsing the request.
190234 req.content.seek(SEEK_END)
191 req.postpath = list(map(unquote, path[1:].split(b"/")))
192235
193236 if access_token:
194237 req.requestHeaders.addRawHeader(
210253 # Assume the body is JSON
211254 req.requestHeaders.addRawHeader(b"Content-Type", b"application/json")
212255
256 if custom_headers:
257 for k, v in custom_headers:
258 req.requestHeaders.addRawHeader(k, v)
259
213260 req.requestReceived(method, path, b"1.1")
214261
262 if await_result:
263 channel.await_result()
264
215265 return req, channel
216
217
218 def wait_until_result(clock, request, timeout=100):
219 """
220 Wait until the request is finished.
221 """
222 clock.run()
223 x = 0
224
225 while not request.finished:
226
227 # If there's a producer, tell it to resume producing so we get content
228 if request._channel._producer:
229 request._channel._producer.resumeProducing()
230
231 x += 1
232
233 if x > timeout:
234 raise TimedOutException("Timed out waiting for request to finish.")
235
236 clock.advance(0.1)
237
238
239 def render(request, resource, clock):
240 request.render(resource)
241 wait_until_result(clock, request)
242266
243267
244268 @implementer(IReactorPluggableNameResolver)
7272 request, channel = self.make_request(
7373 "GET", "/_matrix/client/r0/sync", access_token=self.access_token
7474 )
75 self.render(request)
7675 self.assertEqual(channel.code, 200)
7776
7877 # Get the Room ID to join
8483 "/_matrix/client/r0/rooms/" + room_id + "/join",
8584 access_token=self.access_token,
8685 )
87 self.render(request)
8886 self.assertEqual(channel.code, 200)
8987
9088 # Sync again, to get the message in the room
9189 request, channel = self.make_request(
9290 "GET", "/_matrix/client/r0/sync", access_token=self.access_token
9391 )
94 self.render(request)
9592 self.assertEqual(channel.code, 200)
9693
9794 # Get the message
305305 tok = self.login("user", "password")
306306
307307 request, channel = self.make_request("GET", "/sync?timeout=0", access_token=tok)
308 self.render(request)
309308
310309 invites = channel.json_body["rooms"]["invite"]
311310 self.assertEqual(len(invites), 0, invites)
319318 # Sync again to retrieve the events in the room, so we can check whether this
320319 # room has a notice in it.
321320 request, channel = self.make_request("GET", "/sync?timeout=0", access_token=tok)
322 self.render(request)
323321
324322 # Scan the events in the room to search for a message from the server notices
325323 # user.
357355 request, channel = self.make_request(
358356 "GET", "/sync?timeout=0", access_token=tok,
359357 )
360 self.render(request)
361358
362359 # Also retrieves the list of invites for this user. We don't care about that
363360 # one except if we're processing the last user, which should have received an
308308 )
309309 self.assertTrue(len(latest_event_ids) < 10, len(latest_event_ids))
310310
311 @patch("synapse.handlers.message._DUMMY_EVENT_ROOM_EXCLUSION_EXPIRY", new=0)
312 def test_send_dummy_event_without_consent(self):
313 self._create_extremity_rich_graph()
314 self._enable_consent_checking()
315
316 # Pump the reactor repeatedly so that the background updates have a
317 # chance to run. Attempt to add dummy event with user that has not consented
318 # Check that dummy event send fails.
319 self.pump(10 * 60)
320 latest_event_ids = self.get_success(
321 self.store.get_latest_event_ids_in_room(self.room_id)
322 )
323 self.assertTrue(len(latest_event_ids) == self.EXTREMITIES_COUNT)
324
325 # Create new user, and add consent
326 user2 = self.register_user("user2", "password")
327 token2 = self.login("user2", "password")
328 self.get_success(
329 self.store.user_set_consent_version(user2, self.CONSENT_VERSION)
330 )
331 self.helper.join(self.room_id, user2, tok=token2)
332
333 # Background updates should now cause a dummy event to be added to the graph
334 self.pump(10 * 60)
335
336 latest_event_ids = self.get_success(
337 self.store.get_latest_event_ids_in_room(self.room_id)
338 )
339 self.assertTrue(len(latest_event_ids) < 10, len(latest_event_ids))
340
341311 @patch("synapse.handlers.message._DUMMY_EVENT_ROOM_EXCLUSION_EXPIRY", new=250)
342312 def test_expiry_logic(self):
343313 """Simple test to ensure that _expire_rooms_to_exclude_from_dummy_event_insertion()
2020 from synapse.rest.client.v1 import login
2121
2222 from tests import unittest
23 from tests.server import make_request
2324 from tests.test_utils import make_awaitable
2425 from tests.unittest import override_config
2526
407408 # Advance to a known time
408409 self.reactor.advance(123456 - self.reactor.seconds())
409410
410 request, channel = self.make_request(
411 headers1 = {b"User-Agent": b"Mozzila pizza"}
412 headers1.update(headers)
413
414 make_request(
415 self.reactor,
416 self.site,
411417 "GET",
412 "/_matrix/client/r0/admin/users/" + self.user_id,
418 "/_synapse/admin/v1/users/" + self.user_id,
413419 access_token=access_token,
420 custom_headers=headers1.items(),
414421 **make_request_args,
415422 )
416 request.requestHeaders.addRawHeader(b"User-Agent", b"Mozzila pizza")
417
418 # Add the optional headers
419 for h, v in headers.items():
420 request.requestHeaders.addRawHeader(h, v)
421 self.render(request)
422423
423424 # Advance so the save loop occurs
424425 self.reactor.advance(100)
201201 )
202202
203203 request, channel = self.make_request("POST", "/register", request_data)
204 self.render(request)
205204
206205 if channel.code != 200:
207206 raise HttpResponseException(
214213
215214 def do_sync_for_user(self, token):
216215 request, channel = self.make_request("GET", "/sync", access_token=token)
217 self.render(request)
218216
219217 if channel.code != 200:
220218 raise HttpResponseException(
2525
2626 from tests import unittest
2727 from tests.server import (
28 FakeSite,
2829 ThreadedMemoryReactorClock,
2930 make_request,
30 render,
3131 setup_test_homeserver,
3232 )
3333
6161 )
6262
6363 request, channel = make_request(
64 self.reactor, b"GET", b"/_matrix/foo/%E2%98%83?a=%E2%98%83"
65 )
66 render(request, res, self.reactor)
64 self.reactor, FakeSite(res), b"GET", b"/_matrix/foo/%E2%98%83?a=%E2%98%83"
65 )
6766
6867 self.assertEqual(request.args, {b"a": ["\N{SNOWMAN}".encode("utf8")]})
6968 self.assertEqual(got_kwargs, {"room_id": "\N{SNOWMAN}"})
8281 "GET", [re.compile("^/_matrix/foo$")], _callback, "test_servlet"
8382 )
8483
85 request, channel = make_request(self.reactor, b"GET", b"/_matrix/foo")
86 render(request, res, self.reactor)
84 _, channel = make_request(self.reactor, FakeSite(res), b"GET", b"/_matrix/foo")
8785
8886 self.assertEqual(channel.result["code"], b"500")
8987
107105 "GET", [re.compile("^/_matrix/foo$")], _callback, "test_servlet"
108106 )
109107
110 request, channel = make_request(self.reactor, b"GET", b"/_matrix/foo")
111 render(request, res, self.reactor)
108 _, channel = make_request(self.reactor, FakeSite(res), b"GET", b"/_matrix/foo")
112109
113110 self.assertEqual(channel.result["code"], b"500")
114111
126123 "GET", [re.compile("^/_matrix/foo$")], _callback, "test_servlet"
127124 )
128125
129 request, channel = make_request(self.reactor, b"GET", b"/_matrix/foo")
130 render(request, res, self.reactor)
126 _, channel = make_request(self.reactor, FakeSite(res), b"GET", b"/_matrix/foo")
131127
132128 self.assertEqual(channel.result["code"], b"403")
133129 self.assertEqual(channel.json_body["error"], "Forbidden!!one!")
149145 "GET", [re.compile("^/_matrix/foo$")], _callback, "test_servlet"
150146 )
151147
152 request, channel = make_request(self.reactor, b"GET", b"/_matrix/foobar")
153 render(request, res, self.reactor)
148 _, channel = make_request(
149 self.reactor, FakeSite(res), b"GET", b"/_matrix/foobar"
150 )
154151
155152 self.assertEqual(channel.result["code"], b"400")
156153 self.assertEqual(channel.json_body["error"], "Unrecognized request")
172169 )
173170
174171 # The path was registered as GET, but this is a HEAD request.
175 request, channel = make_request(self.reactor, b"HEAD", b"/_matrix/foo")
176 render(request, res, self.reactor)
172 _, channel = make_request(self.reactor, FakeSite(res), b"HEAD", b"/_matrix/foo")
177173
178174 self.assertEqual(channel.result["code"], b"200")
179175 self.assertNotIn("body", channel.result)
195191
196192 def _make_request(self, method, path):
197193 """Create a request from the method/path and return a channel with the response."""
198 request, channel = make_request(self.reactor, method, path, shorthand=False)
199 request.prepath = [] # This doesn't get set properly by make_request.
200
201194 # Create a site and query for the resource.
202195 site = SynapseSite(
203196 "test",
206199 self.resource,
207200 "1.0",
208201 )
209 request.site = site
210 resource = site.getResourceFor(request)
211
212 # Finally, render the resource and return the channel.
213 render(request, resource, self.reactor)
202
203 # render the request and return the channel
204 _, channel = make_request(self.reactor, site, method, path, shorthand=False)
214205 return channel
215206
216207 def test_unknown_options_request(self):
283274 res = WrapHtmlRequestHandlerTests.TestResource()
284275 res.callback = callback
285276
286 request, channel = make_request(self.reactor, b"GET", b"/path")
287 render(request, res, self.reactor)
277 _, channel = make_request(self.reactor, FakeSite(res), b"GET", b"/path")
288278
289279 self.assertEqual(channel.result["code"], b"200")
290280 body = channel.result["body"]
302292 res = WrapHtmlRequestHandlerTests.TestResource()
303293 res.callback = callback
304294
305 request, channel = make_request(self.reactor, b"GET", b"/path")
306 render(request, res, self.reactor)
295 _, channel = make_request(self.reactor, FakeSite(res), b"GET", b"/path")
307296
308297 self.assertEqual(channel.result["code"], b"301")
309298 headers = channel.result["headers"]
324313 res = WrapHtmlRequestHandlerTests.TestResource()
325314 res.callback = callback
326315
327 request, channel = make_request(self.reactor, b"GET", b"/path")
328 render(request, res, self.reactor)
316 _, channel = make_request(self.reactor, FakeSite(res), b"GET", b"/path")
329317
330318 self.assertEqual(channel.result["code"], b"304")
331319 headers = channel.result["headers"]
344332 res = WrapHtmlRequestHandlerTests.TestResource()
345333 res.callback = callback
346334
347 request, channel = make_request(self.reactor, b"HEAD", b"/path")
348 render(request, res, self.reactor)
335 _, channel = make_request(self.reactor, FakeSite(res), b"HEAD", b"/path")
349336
350337 self.assertEqual(channel.result["code"], b"200")
351338 self.assertNotIn("body", channel.result)
168168 "get_state_handler",
169169 "get_clock",
170170 "get_state_resolution_handler",
171 "hostname",
171172 ]
172173 )
173174 hs.config = default_config("tesths", True)
5353 # Do a UI auth request
5454 request_data = json.dumps({"username": "kermit", "password": "monkey"})
5555 request, channel = self.make_request(b"POST", self.url, request_data)
56 self.render(request)
5756
5857 self.assertEquals(channel.result["code"], b"401", channel.result)
5958
9796 self.registration_handler.check_username = Mock(return_value=True)
9897
9998 request, channel = self.make_request(b"POST", self.url, request_data)
100 self.render(request)
10199
102100 # We don't bother checking that the response is correct - we'll leave that to
103101 # other tests. We just want to make sure we're on the right path.
115113 }
116114 )
117115 request, channel = self.make_request(b"POST", self.url, request_data)
118 self.render(request)
119116
120117 # We're interested in getting a response that looks like a successful
121118 # registration, not so much that the details are exactly what we want.
2929 from twisted.python.failure import Failure
3030 from twisted.python.threadpool import ThreadPool
3131 from twisted.trial import unittest
32 from twisted.web.resource import Resource
3233
3334 from synapse.api.constants import EventTypes, Membership
3435 from synapse.config.homeserver import HomeServerConfig
4647 from synapse.types import UserID, create_requester
4748 from synapse.util.ratelimitutils import FederationRateLimiter
4849
49 from tests.server import (
50 FakeChannel,
51 get_clock,
52 make_request,
53 render,
54 setup_test_homeserver,
55 )
50 from tests.server import FakeChannel, get_clock, make_request, setup_test_homeserver
5651 from tests.test_utils import event_injection, setup_awaitable_errors
5752 from tests.test_utils.logging_setup import setup_logging
5853 from tests.utils import default_config, setupdb
238233 if not isinstance(self.hs, HomeServer):
239234 raise Exception("A homeserver wasn't returned, but %r" % (self.hs,))
240235
241 # Register the resources
242 self.resource = self.create_test_json_resource()
243
244 # create a site to wrap the resource.
236 # create the root resource, and a site to wrap it.
237 self.resource = self.create_test_resource()
245238 self.site = SynapseSite(
246239 logger_name="synapse.access.http.fake",
247240 site_tag=self.hs.config.server.server_name,
252245
253246 from tests.rest.client.v1.utils import RestHelper
254247
255 self.helper = RestHelper(self.hs, self.resource, getattr(self, "user_id", None))
248 self.helper = RestHelper(self.hs, self.site, getattr(self, "user_id", None))
256249
257250 if hasattr(self, "user_id"):
258251 if self.hijack_auth:
322315 hs = self.setup_test_homeserver()
323316 return hs
324317
325 def create_test_json_resource(self):
326 """
327 Create a test JsonResource, with the relevant servlets registerd to it
328
329 The default implementation calls each function in `servlets` to do the
330 registration.
331
332 Returns:
333 JsonResource:
318 def create_test_resource(self) -> Resource:
319 """
320 Create a the root resource for the test server.
321
322 The default implementation creates a JsonResource and calls each function in
323 `servlets` to register servletes against it
334324 """
335325 resource = JsonResource(self.hs)
336326
380370 shorthand: bool = True,
381371 federation_auth_origin: str = None,
382372 content_is_form: bool = False,
373 await_result: bool = True,
383374 ) -> Tuple[SynapseRequest, FakeChannel]:
384375 ...
385376
394385 shorthand: bool = True,
395386 federation_auth_origin: str = None,
396387 content_is_form: bool = False,
388 await_result: bool = True,
397389 ) -> Tuple[T, FakeChannel]:
398390 ...
399391
407399 shorthand: bool = True,
408400 federation_auth_origin: str = None,
409401 content_is_form: bool = False,
402 await_result: bool = True,
410403 ) -> Tuple[T, FakeChannel]:
411404 """
412405 Create a SynapseRequest at the path using the method and containing the
425418 content_is_form: Whether the content is URL encoded form data. Adds the
426419 'Content-Type': 'application/x-www-form-urlencoded' header.
427420
421 await_result: whether to wait for the request to complete rendering. If
422 true (the default), will pump the test reactor until the the renderer
423 tells the channel the request is finished.
424
428425 Returns:
429426 Tuple[synapse.http.site.SynapseRequest, channel]
430427 """
431 if isinstance(content, dict):
432 content = json.dumps(content).encode("utf8")
433
434428 return make_request(
435429 self.reactor,
430 self.site,
436431 method,
437432 path,
438433 content,
441436 shorthand,
442437 federation_auth_origin,
443438 content_is_form,
444 )
445
446 def render(self, request):
447 """
448 Render a request against the resources registered by the test class's
449 servlets.
450
451 Args:
452 request (synapse.http.site.SynapseRequest): The request to render.
453 """
454 render(request, self.resource, self.reactor)
439 await_result,
440 )
455441
456442 def setup_test_homeserver(self, *args, **kwargs):
457443 """
567553 self.hs.config.registration_shared_secret = "shared"
568554
569555 # Create the user
570 request, channel = self.make_request("GET", "/_matrix/client/r0/admin/register")
571 self.render(request)
556 request, channel = self.make_request("GET", "/_synapse/admin/v1/register")
572557 self.assertEqual(channel.code, 200, msg=channel.result)
573558 nonce = channel.json_body["nonce"]
574559
594579 }
595580 )
596581 request, channel = self.make_request(
597 "POST", "/_matrix/client/r0/admin/register", body.encode("utf8")
598 )
599 self.render(request)
582 "POST", "/_synapse/admin/v1/register", body.encode("utf8")
583 )
600584 self.assertEqual(channel.code, 200, channel.json_body)
601585
602586 user_id = channel.json_body["user_id"]
615599 request, channel = self.make_request(
616600 "POST", "/_matrix/client/r0/login", json.dumps(body).encode("utf8")
617601 )
618 self.render(request)
619602 self.assertEqual(channel.code, 200, channel.result)
620603
621604 access_token = channel.json_body["access_token"]
684667 request, channel = self.make_request(
685668 "POST", "/_matrix/client/r0/login", json.dumps(body).encode("utf8")
686669 )
687 self.render(request)
688670 self.assertEqual(channel.code, 403, channel.result)
689671
690672 def inject_room_member(self, room: str, user: str, membership: Membership) -> None:
270270
271271 # Install @cache_in_self attributes
272272 for key, val in kwargs.items():
273 setattr(hs, key, val)
273 setattr(hs, "_" + key, val)
274274
275275 # Mock TLS
276276 hs.tls_server_context_factory = Mock()