Compare commits

...

158 Commits

Author SHA1 Message Date
Michael Kaye
76b7252178 Remove commas and None, to please pep8 2019-04-04 17:36:31 +01:00
Michael Kaye
3b082bf3b2 Prevent exception when hs or shadow_hs missing 2019-04-04 17:30:37 +01:00
Erik Johnston
b00a8d870c Fix registration with register_mxid_from_3pid enabled (#5011) 2019-04-04 16:45:15 +01:00
Michael Kaye
8fa09c7479 Rewrite more internal IS urls. (#5006)
* Rewrite more internal IS urls.
2019-04-04 15:28:37 +01:00
Erik Johnston
d94873d525 Merge pull request #4997 from matrix-org/erikj/dinsic_email_display_name
Add config option to use email as display name for new users
2019-04-03 10:57:20 +01:00
Erik Johnston
f2fa172375 Sample config 2019-04-03 10:40:40 +01:00
Erik Johnston
8c0ebe3026 Add config option to use email as display name for new users 2019-04-03 10:26:56 +01:00
Erik Johnston
53dd358c83 Merge pull request #4910 from matrix-org/erikj/third_party_invite_create_spam
Add third party invite support to spam checker
2019-03-21 16:07:27 +00:00
Erik Johnston
f5c944c7f2 Fix comments 2019-03-21 15:31:34 +00:00
Erik Johnston
aefb7a1146 Update sample config 2019-03-21 12:52:22 +00:00
Erik Johnston
479b7b1eff Add another test 2019-03-21 12:48:37 +00:00
Erik Johnston
f04ee0b351 Run black on tests/rulecheck/ 2019-03-21 12:48:37 +00:00
Erik Johnston
164798ec32 Add 3PID invite support to spam checker 2019-03-21 12:48:37 +00:00
Erik Johnston
b7d7d20a38 Correctly handle 3PID invites in create room spam check
We also add an option to outright deny third party invites
2019-03-21 12:48:37 +00:00
Erik Johnston
2bb6d85736 Turn off newsfile check 2019-03-21 12:48:25 +00:00
Erik Johnston
0f0671e5e0 Merge branch 'develop' of github.com:matrix-org/synapse into erikj/dinsic-merged 2019-03-21 12:41:42 +00:00
Erik Johnston
8eb9f37a01 Merge pull request #4875 from matrix-org/erikj/spam_checker
Extend spam checking rules
2019-03-18 16:28:46 +00:00
Erik Johnston
ea89e73ebf Add unit tests 2019-03-18 15:56:08 +00:00
Erik Johnston
68a9d1fc34 Add rules to DomainRuleChecker 2019-03-18 15:56:04 +00:00
Erik Johnston
feae387576 Don't spam check actions by admins 2019-03-18 15:51:00 +00:00
Erik Johnston
e64f7c0188 Run black on tests/rulecheck/test_domainrulecheck.py 2019-03-18 15:51:00 +00:00
Erik Johnston
b85ff4b894 Add user_may_join_room spam check 2019-03-18 15:51:00 +00:00
Erik Johnston
4eca8d3fb3 Add invite_list and cloning param to create room rule 2019-03-18 15:50:55 +00:00
Michael Kaye
aa3dc78f65 Merge pull request #4862 from matrix-org/erikj/dinsic-merged-master
Merge master into dinsic
2019-03-15 17:21:22 +01:00
Erik Johnston
de874364e7 Fixup config 2019-03-15 14:41:27 +00:00
Erik Johnston
7e94e2ad94 Disable changelog CI checks 2019-03-15 14:40:28 +00:00
Erik Johnston
e7ec6f78ca Manifest 2019-03-15 14:39:25 +00:00
Erik Johnston
7a9aa4b81b Isort 2019-03-15 14:39:25 +00:00
Erik Johnston
5827e976fe Fixups 2019-03-15 14:39:25 +00:00
Erik Johnston
44c0661d97 Merge branch 'master' of github.com:matrix-org/synapse into erikj/dinsic-merged-master 2019-03-15 14:36:44 +00:00
Erik Johnston
85db96cc81 Add some debug logging for device list handling 2019-03-08 12:31:41 +00:00
Andrew Morgan
3271742905 Merge pull request #4681 from matrix-org/dinsic_anoa/info_split
[DINSIC] Use internal-info for identity server
2019-02-28 11:28:55 +00:00
Andrew Morgan
0a23bf442f Merge branch 'dinsic_anoa/info_split' of github.com:matrix-org/synapse into dinsic_anoa/info_split 2019-02-27 15:23:19 +00:00
Andrew Morgan
9b13038d05 Check shadow_hs as well as hs during 3pid reg 2019-02-27 15:22:41 +00:00
Andrew Morgan
dfe09ec313 Merge pull request #4747 from matrix-org/anoa/public_rooms_federate
[DINSIC] Prevent showing non-fed rooms in fed /publicRooms
2019-02-26 14:40:45 +00:00
Richard van der Hoff
13bc1e5307 Update synapse/util/threepids.py
Co-Authored-By: anoadragon453 <1342360+anoadragon453@users.noreply.github.com>
2019-02-25 16:08:58 +00:00
Andrew Morgan
70e039c7ae Use internal-info for identity server. Block reg on fields 2019-02-19 15:29:49 +00:00
Michael Kaye
2712a9ef8f Fix to keep the signature valid for the request. (#4480) 2019-01-28 14:24:28 +00:00
Michael Kaye
da757b7759 Rewrite identity server URLs as required. (#4393)
This takes a mapping of <old> -> <new> and looks up and replaces as
required.
2019-01-21 15:48:15 +00:00
Michael Kaye
59bc7debf0 When performing shadow registration, don't inhibit login. (#4385) 2019-01-14 18:01:34 +00:00
Matthew Hodgson
cf68593544 Synchronise account metadata onto another server. (#4145)
* implement shadow registration via AS (untested)
* shadow support for 3pid binding/unbinding (untested)
2019-01-11 15:50:28 +00:00
Michael Kaye
9cc95fd0a5 Merge pull request #4148 from matrix-org/matthew/red_list
add im.vector.hide_profile to user account_data
2018-12-19 15:42:19 +00:00
Matthew Hodgson
82886e4c8f hide_profile: false should mean not to hide the profile :/ 2018-12-12 00:57:39 +00:00
Matthew Hodgson
08919847c1 fix sense of 'hide' 2018-11-04 23:49:57 +00:00
Matthew Hodgson
c3acc45a87 pull in profile_handler correctly 2018-11-04 23:49:19 +00:00
Matthew Hodgson
ae5bb32ad0 add im.vector.hide_profile to user account_data 2018-11-04 23:46:11 +00:00
Matthew Hodgson
7ed3232b08 fix tests 2018-11-03 12:58:25 +00:00
Matthew Hodgson
6e7488ce11 merge master into dinsic, again... 2018-11-03 12:14:24 +00:00
Michael Kaye
41585e1340 Merge pull request #4047 from matrix-org/michaelkaye/dinsic_allow_user_directory_url
user_directory.defer_to_id_server should be an URL, not a hostname
2018-11-02 12:00:18 +00:00
Matthew Hodgson
9498cd3e7b fix conflict and reinstate 6372dff771 2018-10-22 20:27:25 +02:00
Matthew Hodgson
c7503f8f33 merge in master 2018-10-22 20:19:40 +02:00
Michael Kaye
9d8baa1595 Allow us to configure http vs https for user_directory 2018-10-17 11:38:48 +01:00
Matthew Hodgson
4ff8486f0f fix missing import and run isort 2018-07-20 11:29:18 +01:00
David Baker
2669e494e0 Merge remote-tracking branch 'origin/master' into dinsic 2018-07-19 18:25:25 +01:00
David Baker
b6d8a808a4 Merge pull request #3557 from matrix-org/dbkr/delete_profiles
Remove deactivated users from profile search
2018-07-19 15:58:40 +01:00
David Baker
0cb5d34756 Hopefully fix postgres 2018-07-19 15:12:48 +01:00
David Baker
650761666d More run_on_reactor 2018-07-19 14:52:35 +01:00
David Baker
aa2a4b4b42 run_on_reactor is dead 2018-07-19 14:48:24 +01:00
David Baker
022469d819 Change column def so it works on pgsql & sqlite
Now I remember discovering previously there was no way to make boolean
columns work
2018-07-19 10:28:26 +01:00
David Baker
45d06c754a Add hopefully enlightening comment 2018-07-18 20:52:21 +01:00
David Baker
dbd0821c43 Oops, didn't mean to commit that 2018-07-18 20:50:20 +01:00
David Baker
0476852fc6 Remove deactivated users from profile search 2018-07-18 18:05:29 +01:00
David Baker
1d11d9323d Merge remote-tracking branch 'origin/master' into dinsic 2018-07-17 15:43:33 +01:00
Michael Kaye
261e4f2542 Merge pull request #3502 from matrix-org/matthew/dinsic-tweak-display-names
tweak dinsic display names
2018-07-10 15:59:26 +01:00
Matthew Hodgson
11728561f3 improve domain matches 2018-07-10 15:21:14 +01:00
Matthew Hodgson
9d57abcadd fix bounds error 2018-07-10 13:58:01 +01:00
Matthew Hodgson
cb0bbde981 tweak dinsic display names 2018-07-10 13:56:32 +01:00
Matthew Hodgson
abc97bd1de Merge pull request #3487 from matrix-org/matthew/dinsic-encrypt-for-invited-users
Query the device lists of users when they are invited to a room.
2018-07-10 12:21:57 +01:00
Matthew Hodgson
ee238254a0 Query the device lists of users when they are invited to a room.
Previously we only queried the device list when the user joined the room; now we
do it when they are invited too.  This means that new messages can be encrypted
for the devices of the invited user as of the point they were invited.

WARNING: This commit has two major problems however:
 1. If the invited user adds devices after being invited but before joining, the
    device-list will not be updated to the other servers in the room (as we don't
    know who those servers are).
 2. This introduces a regression, as previously the device-list would be correctly
    updated when when user joined the room.  However, this resync doesn't happen
    now, so devices which joined after the invite and before the join may never
    be added to the device-list.

This is being merged for DINSIC given the edge case of adding devices between
invite & join is pretty rare in their use case, but before it can be merged to
synapse in general we need to at least re-sync the devicelist when the user joins
or to implement some kind of pubsub mechanism to let interested servers subscribe
to devicelist updates on other servers irrespective of user join/invite membership.

This was originally https://github.com/matrix-org/synapse/pull/3484
2018-07-06 16:29:08 +02:00
Matthew Hodgson
0125b5d002 typos 2018-06-25 17:37:00 +01:00
Michael Kaye
fe265fe990 Merge tag 'v0.31.2' into dinsic 2018-06-22 17:04:50 +01:00
David Baker
7735eee41d Merge pull request #3426 from matrix-org/dbkr/e2e_by_default
Server-enforced e2e for private rooms
2018-06-22 16:49:42 +01:00
David Baker
3d0faa39fb Add m.encryption to event types 2018-06-22 16:47:49 +01:00
David Baker
fd28d13e19 Server-enforced e2e for private rooms 2018-06-22 13:54:17 +01:00
Michael Kaye
d18731e252 Merge pull request #3202 from matrix-org/michaelkaye/domain_based_rule_checker
DomainRuleChecker
2018-05-21 09:32:47 +01:00
Michael Kaye
81beae30b8 Update with documentation suggestions 2018-05-18 16:12:22 +01:00
Michael Kaye
11f1bace3c Address PR feedback
- add code and test to handle configuration of an empty array
- move docstrings around and update class level documentation
2018-05-11 12:51:03 +01:00
Michael Kaye
1e8cfc9e77 pep8 style fixes 2018-05-09 15:11:19 +01:00
Michael Kaye
488ed3e444 Generic "are users in domain X allowed to invite users in domain Y" logic 2018-05-09 14:50:48 +01:00
Matthew Hodgson
c3ec84dbcd Merge pull request #3096 from matrix-org/matthew/derive-mxid-from-3pid
add the register_mxid_from_3pid setting (untested)
2018-05-04 02:26:46 +01:00
Matthew Hodgson
0783801659 unbreak tests 2018-05-04 02:18:01 +01:00
Matthew Hodgson
9f2fd29c14 fix double negative 2018-05-04 02:11:22 +01:00
Matthew Hodgson
6372dff771 remove create_profile from tests 2018-05-04 01:58:45 +01:00
Matthew Hodgson
b3e346f40c don't pass a requester if we don't have one to set_displayname 2018-05-04 01:56:01 +01:00
Matthew Hodgson
fb47ce3e6a make set_profile_* an upsert rather than update, now create_profile is gone 2018-05-04 01:46:26 +01:00
Matthew Hodgson
debf04556b fix user in user regexp 2018-05-04 01:15:33 +01:00
Matrix
907a62df28 fix strip_invalid_mxid_characters 2018-05-03 23:54:36 +00:00
Matrix
41b987cbc5 unbreak 3pid deletion 2018-05-03 23:54:36 +00:00
Matthew Hodgson
5c74ab4064 fix user_id / user confusion 2018-05-04 00:53:56 +01:00
Matrix
06820250c9 unbreak 3pid deletion 2018-05-03 23:27:34 +00:00
Matthew Hodgson
383c4ae59c Merge branch 'dinsic' into matthew/derive-mxid-from-3pid 2018-05-03 23:39:08 +01:00
Matthew Hodgson
f639ac143d Merge pull request #3180 from matrix-org/matthew/disable-3pid-changes
add option to disable changes to the 3PIDs for an account.
2018-05-03 18:19:58 +01:00
Matthew Hodgson
ad0424bab0 Merge pull request #3179 from matrix-org/matthew/disable-set-profile
options to disable setting profile info
2018-05-03 18:19:48 +01:00
Matthew Hodgson
2992125561 special case msisdns when deriving mxids from 3pids 2018-05-03 17:52:46 +01:00
David Baker
ef56b6e27c Merge pull request #3185 from matrix-org/dbkr/change_profile_replication_uri
Change profile replication URI
2018-05-03 15:17:51 +01:00
David Baker
53d6245529 Change profile replication URI 2018-05-03 14:55:40 +01:00
Matthew Hodgson
25e471dac3 fix defaults in config example 2018-05-03 11:46:56 +01:00
Matthew Hodgson
76fca1730e fix defaults in example config 2018-05-03 11:46:11 +01:00
Matthew Hodgson
32e4420a66 improve mxid & displayname selection for register_mxid_from_3pid
* [x] strip invalid characters from generated mxid
* [x] append numbers to disambiguate clashing mxids
* [x] generate displayanames from 3pids using a dodgy heuristic
* [x] get rid of the create_profile_with_localpart and instead
      explicitly set displaynames so they propagate correctly
2018-05-03 04:21:20 +01:00
Matthew Hodgson
79b2583f1b Merge branch 'dinsic' into matthew/derive-mxid-from-3pid 2018-05-03 02:51:49 +01:00
Matthew Hodgson
8a24c4eee5 add option to disable changes to the 3PIDs for an account.
This only considers the /account/3pid API, which should be sufficient
as currently we can't change emails associated with push notifs
(which are provisioned at registration), and we can't directly create
mappings for accounts in an IS other than by answering an invite
2018-05-03 02:47:55 +01:00
Matthew Hodgson
f93cb7410d options to disable setting profile info 2018-05-03 01:29:12 +01:00
Matthew Hodgson
50d5a97c1b Merge branch 'master' into dinsic 2018-05-03 00:26:33 +01:00
David Baker
c06932a029 Merge pull request #3166 from matrix-org/dbkr/postgres_doesnt_have_ifnull
Use COALESCE rather than IFNULL
2018-05-01 18:15:28 +01:00
David Baker
3a62cacfb0 Use COALESCE rather than IFNULL
as this works on sqlite and postgres (postgres doesn't have IFNULL)
2018-05-01 17:54:03 +01:00
David Baker
4d55b16faa Fix python synatx 2018-05-01 14:32:30 +01:00
David Baker
105709bf32 Fix profile repl 2018-05-01 14:27:14 +01:00
David Baker
d7fad867fa Merge pull request #3123 from matrix-org/dbkr/user_directory_defer_to_is
Option to defer user_directory search to an ID server
2018-04-27 17:18:13 +01:00
David Baker
8fddcf703e Merge remote-tracking branch 'origin/dinsic' into dbkr/user_directory_defer_to_is 2018-04-26 10:23:12 +01:00
David Baker
e2adb360eb Merge pull request #3112 from matrix-org/dbkr/profile_replication
Option to replicate user profiles to another server
2018-04-26 10:22:28 +01:00
David Baker
47ed4a4aa7 PR feedback
Unnecessary inlineCallbacks, missing yield, SQL comments & trailing
commas.
2018-04-25 13:58:37 +01:00
David Baker
7fafa838ae Comment why the looping call loops 2018-04-25 11:59:22 +01:00
David Baker
de341bec1b Add 'ex[erimental API' comment 2018-04-25 11:51:57 +01:00
David Baker
643c89d497 Fix spelling & add experimental API comment 2018-04-25 11:40:37 +01:00
David Baker
6554253f48 Option to defer to an ID server for user_directory 2018-04-19 19:28:12 +01:00
David Baker
3add16df49 pep8 again 2018-04-17 13:23:16 +01:00
David Baker
dde01efbcb Don't do profile repl if no repl targets 2018-04-17 12:26:45 +01:00
David Baker
22e416b726 Update profile cache only on master
and same for the profile replication
2018-04-17 12:17:16 +01:00
David Baker
b4b7c80181 Fix other tests 2018-04-17 11:03:10 +01:00
David Baker
5fc3477fd3 Fix tests 2018-04-17 10:46:49 +01:00
David Baker
8743f42b49 pep8 2018-04-17 10:34:04 +01:00
David Baker
7285afa4be Handle current batch number being null 2018-04-17 10:28:00 +01:00
Matthew Hodgson
b22a53e357 turn @'s to -'s rather than .'s 2018-04-17 09:32:42 +01:00
David Baker
3c446d0a81 Merge remote-tracking branch 'origin/dinsic' into dbkr/profile_replication 2018-04-16 18:35:25 +01:00
Matthew Hodgson
240e940c3f handle medium checks correctly 2018-04-12 18:30:32 +01:00
Matthew Hodgson
969ed2e49d add the register_mxid_from_3pid setting (untested) 2018-04-12 18:20:51 +01:00
David Baker
1147ce7e18 Include origin_server in the sig!
Also be consistent with underscores
2018-04-12 17:59:37 +01:00
Matthew Hodgson
0d2b7fdcec Merge branch 'develop' into dinsic 2018-04-12 13:29:57 +01:00
David Baker
4e12b10c7c Trigger profile replication on profile change 2018-04-11 10:17:07 +01:00
David Baker
e654230a51 Written but untested profile replication 2018-04-10 17:41:58 +01:00
Matthew Hodgson
ef5193e0cb Merge pull request #2973 from matrix-org/matthew/dinsic_3pid_check
Delegate 3PID registration determination to experimental IS API
2018-03-14 22:35:58 +00:00
Matthew Hodgson
7b3959c7f3 Merge branch 'develop' into dinsic 2018-03-14 22:31:34 +00:00
Matthew Hodgson
2e4a6c5aab incorporate PR feedback and rename URL 2018-03-14 22:09:08 +00:00
Matthew Hodgson
e3eb2cfe8b Merge branch 'develop' into matthew/dinsic_3pid_check 2018-03-14 21:56:58 +00:00
Matthew Hodgson
5c341c99f6 add 'allow_invited_3pids' option to invited 3PIDs to register 2018-03-13 21:15:14 +00:00
Matthew Hodgson
739d3500fe pep8 2018-03-13 01:50:32 +00:00
Matthew Hodgson
0e2d70e101 typos 2018-03-13 01:41:20 +00:00
Matthew Hodgson
82c4fd7226 add yields 2018-03-13 01:38:02 +00:00
Matthew Hodgson
e446077478 delegate to the IS to check 3PID signup eligibility 2018-03-13 01:34:20 +00:00
Matthew Hodgson
d82c89ac22 fix thinko on 3pid whitelisting 2018-01-24 11:07:24 +01:00
Matthew Hodgson
75b25b3f1f Merge branch 'develop' into dinsic 2018-01-23 10:00:53 +01:00
AmandineLP
1df10d8814 Fixed translation 2018-01-22 21:18:44 +01:00
AmandineLP
8f9340d248 Fixed translation 2018-01-22 21:17:36 +01:00
AmandineLP
c5034cd4b0 More translation 2018-01-22 21:16:46 +01:00
AmandineLP
f7f937d051 Translate to FR 2018-01-22 21:14:13 +01:00
AmandineLP
e52b5d94a9 Translate to FR 2018-01-22 21:13:03 +01:00
AmandineLP
d90f27a21f Translate to FR 2018-01-22 21:12:06 +01:00
AmandineLP
03cf9710e3 Translate to FR 2018-01-22 21:10:00 +01:00
AmandineLP
1dcdd8d568 Translate to FR 2018-01-22 20:02:47 +01:00
AmandineLP
4344fb1faf translate to FR 2018-01-22 20:01:00 +01:00
Matthew Hodgson
846577ebde fork notif templates 2018-01-22 19:55:27 +01:00
Matthew Hodgson
3869981227 remove unreachable except block 2018-01-22 18:43:41 +01:00
Matthew Hodgson
fa80b492a5 fix thinko 2018-01-22 18:43:41 +01:00
Richard van der Hoff
c776c52eed Back out unrelated changes 2018-01-22 16:44:39 +00:00
Matthew Hodgson
b424c16f50 fix tests 2018-01-22 15:25:25 +01:00
Matthew Hodgson
313a489fc9 incorporate PR feedback 2018-01-22 14:54:46 +01:00
Matthew Hodgson
4b090cb273 add federation_domain_whitelist
gives a way to restrict which domains your HS is allowed to federate with.
useful mainly for gracefully preventing a private but internet-connected HS from trying to federate to the wider public Matrix network
2018-01-22 12:13:41 +01:00
Matthew Hodgson
3f79378d4b make replication tests pass on OSX 2018-01-20 17:23:27 +00:00
45 changed files with 1994 additions and 168 deletions

View File

@@ -26,16 +26,6 @@ steps:
- docker#v3.0.1:
image: "python:3.6"
- command:
- "python -m pip install tox"
- "scripts-dev/check-newsfragment"
label: ":newspaper: Newsfile"
branches: "!master !develop !release-*"
plugins:
- docker#v3.0.1:
image: "python:3.6"
propagate-environment: true
- wait
- command:

View File

@@ -43,3 +43,8 @@ prune .buildkite
exclude jenkins*
recursive-exclude jenkins *.sh
# FIXME: we shouldn't have these templates here
recursive-include res/templates-dinsic *.css
recursive-include res/templates-dinsic *.html
recursive-include res/templates-dinsic *.txt

View File

@@ -654,9 +654,32 @@ uploads_path: "DATADIR/uploads"
#
#disable_msisdn_registration: true
# Derive the user's matrix ID from a type of 3PID used when registering.
# This overrides any matrix ID the user proposes when calling /register
# The 3PID type should be present in registrations_require_3pid to avoid
# users failing to register if they don't specify the right kind of 3pid.
#
#register_mxid_from_3pid: email
# Uncomment to set the display name of new users to their email address,
# rather than using the default heuristic.
#
#register_just_use_email_for_display_name: true
# Mandate that users are only allowed to associate certain formats of
# 3PIDs with accounts on this server.
#
# Use an Identity Server to establish which 3PIDs are allowed to register?
# Overrides allowed_local_3pids below.
#
#check_is_for_allowed_local_3pids: matrix.org
#
# If you are using an IS you can also check whether that IS registers
# pending invites for the given 3PID (and then allow it to sign up on
# the platform):
#
#allow_invited_3pids: False
#
#allowed_local_3pids:
# - medium: email
# pattern: '.*@matrix\.org'
@@ -665,6 +688,11 @@ uploads_path: "DATADIR/uploads"
# - medium: msisdn
# pattern: '\+44'
# If true, stop users from trying to change the 3PIDs associated with
# their accounts.
#
#disable_3pid_changes: False
# If set, allows registration of standard or admin accounts by anyone who
# has the shared secret, even if registration is otherwise disabled.
#
@@ -702,6 +730,30 @@ uploads_path: "DATADIR/uploads"
# - matrix.org
# - vector.im
# If enabled, user IDs, display names and avatar URLs will be replicated
# to this server whenever they change.
# This is an experimental API currently implemented by sydent to support
# cross-homeserver user directories.
#
#replicate_user_profiles_to: example.com
# If specified, attempt to replay registrations, profile changes & 3pid
# bindings on the given target homeserver via the AS API. The HS is authed
# via a given AS token.
#
#shadow_server:
# hs_url: https://shadow.example.com
# hs: shadow.example.com
# as_token: 12u394refgbdhivsia
# If enabled, don't let users set their own display names/avatars
# other than for the very first time (unless they are a server admin).
# Useful when provisioning users based on the contents of a 3rd party
# directory and to avoid ambiguities.
#
#disable_set_displayname: False
#disable_set_avatar_url: False
# Users who register on this homeserver will automatically be joined
# to these rooms
#
@@ -975,6 +1027,11 @@ password_config:
#user_directory:
# enabled: true
# search_all_users: false
#
# # If this is set, user search will be delegated to this ID server instead
# # of synapse performing the search itself.
# # This is an experimental API.
# defer_to_id_server: https://id.example.com
# User Consent configuration

View File

@@ -0,0 +1,7 @@
.header {
border-bottom: 4px solid #e4f7ed ! important;
}
.notif_link a, .footer a {
color: #76CFA6 ! important;
}

View File

@@ -0,0 +1,156 @@
body {
margin: 0px;
}
pre, code {
word-break: break-word;
white-space: pre-wrap;
}
#page {
font-family: 'Open Sans', Helvetica, Arial, Sans-Serif;
font-color: #454545;
font-size: 12pt;
width: 100%;
padding: 20px;
}
#inner {
width: 640px;
}
.header {
width: 100%;
height: 87px;
color: #454545;
border-bottom: 4px solid #e5e5e5;
}
.logo {
text-align: right;
margin-left: 20px;
}
.salutation {
padding-top: 10px;
font-weight: bold;
}
.summarytext {
}
.room {
width: 100%;
color: #454545;
border-bottom: 1px solid #e5e5e5;
}
.room_header td {
padding-top: 38px;
padding-bottom: 10px;
border-bottom: 1px solid #e5e5e5;
}
.room_name {
vertical-align: middle;
font-size: 18px;
font-weight: bold;
}
.room_header h2 {
margin-top: 0px;
margin-left: 75px;
font-size: 20px;
}
.room_avatar {
width: 56px;
line-height: 0px;
text-align: center;
vertical-align: middle;
}
.room_avatar img {
width: 48px;
height: 48px;
object-fit: cover;
border-radius: 24px;
}
.notif {
border-bottom: 1px solid #e5e5e5;
margin-top: 16px;
padding-bottom: 16px;
}
.historical_message .sender_avatar {
opacity: 0.3;
}
/* spell out opacity and historical_message class names for Outlook aka Word */
.historical_message .sender_name {
color: #e3e3e3;
}
.historical_message .message_time {
color: #e3e3e3;
}
.historical_message .message_body {
color: #c7c7c7;
}
.historical_message td,
.message td {
padding-top: 10px;
}
.sender_avatar {
width: 56px;
text-align: center;
vertical-align: top;
}
.sender_avatar img {
margin-top: -2px;
width: 32px;
height: 32px;
border-radius: 16px;
}
.sender_name {
display: inline;
font-size: 13px;
color: #a2a2a2;
}
.message_time {
text-align: right;
width: 100px;
font-size: 11px;
color: #a2a2a2;
}
.message_body {
}
.notif_link td {
padding-top: 10px;
padding-bottom: 10px;
font-weight: bold;
}
.notif_link a, .footer a {
color: #454545;
text-decoration: none;
}
.debug {
font-size: 10px;
color: #888;
}
.footer {
margin-top: 20px;
text-align: center;
}

View File

@@ -0,0 +1,45 @@
{% for message in notif.messages %}
<tr class="{{ "historical_message" if message.is_historical else "message" }}">
<td class="sender_avatar">
{% if loop.index0 == 0 or notif.messages[loop.index0 - 1].sender_name != notif.messages[loop.index0].sender_name %}
{% if message.sender_avatar_url %}
<img alt="" class="sender_avatar" src="{{ message.sender_avatar_url|mxc_to_http(32,32) }}" />
{% else %}
{% if message.sender_hash % 3 == 0 %}
<img class="sender_avatar" src="https://vector.im/beta/img/76cfa6.png" />
{% elif message.sender_hash % 3 == 1 %}
<img class="sender_avatar" src="https://vector.im/beta/img/50e2c2.png" />
{% else %}
<img class="sender_avatar" src="https://vector.im/beta/img/f4c371.png" />
{% endif %}
{% endif %}
{% endif %}
</td>
<td class="message_contents">
{% if loop.index0 == 0 or notif.messages[loop.index0 - 1].sender_name != notif.messages[loop.index0].sender_name %}
<div class="sender_name">{% if message.msgtype == "m.emote" %}*{% endif %} {{ message.sender_name }}</div>
{% endif %}
<div class="message_body">
{% if message.msgtype == "m.text" %}
{{ message.body_text_html }}
{% elif message.msgtype == "m.emote" %}
{{ message.body_text_html }}
{% elif message.msgtype == "m.notice" %}
{{ message.body_text_html }}
{% elif message.msgtype == "m.image" %}
<img src="{{ message.image_url|mxc_to_http(640, 480, scale) }}" />
{% elif message.msgtype == "m.file" %}
<span class="filename">{{ message.body_text_plain }}</span>
{% endif %}
</div>
</td>
<td class="message_time">{{ message.ts|format_ts("%H:%M") }}</td>
</tr>
{% endfor %}
<tr class="notif_link">
<td></td>
<td>
<a href="{{ notif.link }}">Voir {{ room.title }}</a>
</td>
<td></td>
</tr>

View File

@@ -0,0 +1,16 @@
{% for message in notif.messages %}
{% if message.msgtype == "m.emote" %}* {% endif %}{{ message.sender_name }} ({{ message.ts|format_ts("%H:%M") }})
{% if message.msgtype == "m.text" %}
{{ message.body_text_plain }}
{% elif message.msgtype == "m.emote" %}
{{ message.body_text_plain }}
{% elif message.msgtype == "m.notice" %}
{{ message.body_text_plain }}
{% elif message.msgtype == "m.image" %}
{{ message.body_text_plain }}
{% elif message.msgtype == "m.file" %}
{{ message.body_text_plain }}
{% endif %}
{% endfor %}
Voir {{ room.title }} à {{ notif.link }}

View File

@@ -0,0 +1,55 @@
<!doctype html>
<html lang="en">
<head>
<style type="text/css">
{% include 'mail.css' without context %}
{% include "mail-%s.css" % app_name ignore missing without context %}
</style>
</head>
<body>
<table id="page">
<tr>
<td> </td>
<td id="inner">
<table class="header">
<tr>
<td>
<div class="salutation">Bonjour {{ user_display_name }},</div>
<div class="summarytext">{{ summary_text }}</div>
</td>
<td class="logo">
{% if app_name == "Riot" %}
<img src="http://matrix.org/img/riot-logo-email.png" width="83" height="83" alt="[Riot]"/>
{% elif app_name == "Vector" %}
<img src="http://matrix.org/img/vector-logo-email.png" width="64" height="83" alt="[Vector]"/>
{% else %}
<img src="http://matrix.org/img/matrix-120x51.png" width="120" height="51" alt="[matrix]"/>
{% endif %}
</td>
</tr>
</table>
{% for room in rooms %}
{% include 'room.html' with context %}
{% endfor %}
<div class="footer">
<a href="{{ unsubscribe_link }}">Se désinscrire</a>
<br/>
<br/>
<div class="debug">
Sending email at {{ reason.now|format_ts("%c") }} due to activity in room {{ reason.room_name }} because
an event was received at {{ reason.received_at|format_ts("%c") }}
which is more than {{ "%.1f"|format(reason.delay_before_mail_ms / (60*1000)) }} ({{ reason.delay_before_mail_ms }}) mins ago,
{% if reason.last_sent_ts %}
and the last time we sent a mail for this room was {{ reason.last_sent_ts|format_ts("%c") }},
which is more than {{ "%.1f"|format(reason.throttle_ms / (60*1000)) }} (current throttle_ms) mins ago.
{% else %}
and we don't have a last time we sent a mail for this room.
{% endif %}
</div>
</div>
</td>
<td> </td>
</tr>
</table>
</body>
</html>

View File

@@ -0,0 +1,10 @@
Bonjour {{ user_display_name }},
{{ summary_text }}
{% for room in rooms %}
{% include 'room.txt' with context %}
{% endfor %}
Vous pouvez désactiver ces notifications en cliquant ici {{ unsubscribe_link }}

View File

@@ -0,0 +1,33 @@
<table class="room">
<tr class="room_header">
<td class="room_avatar">
{% if room.avatar_url %}
<img alt="" src="{{ room.avatar_url|mxc_to_http(48,48) }}" />
{% else %}
{% if room.hash % 3 == 0 %}
<img alt="" src="https://vector.im/beta/img/76cfa6.png" />
{% elif room.hash % 3 == 1 %}
<img alt="" src="https://vector.im/beta/img/50e2c2.png" />
{% else %}
<img alt="" src="https://vector.im/beta/img/f4c371.png" />
{% endif %}
{% endif %}
</td>
<td class="room_name" colspan="2">
{{ room.title }}
</td>
</tr>
{% if room.invite %}
<tr>
<td></td>
<td>
<a href="{{ room.link }}">Rejoindre la conversation.</a>
</td>
<td></td>
</tr>
{% else %}
{% for notif in room.notifs %}
{% include 'notif.html' with context %}
{% endfor %}
{% endif %}
</table>

View File

@@ -0,0 +1,9 @@
{{ room.title }}
{% if room.invite %}
  Vous avez été invité, rejoignez la conversation en cliquant sur le lien suivant {{ room.link }}
{% else %}
{% for notif in room.notifs %}
{% include 'notif.txt' with context %}
{% endfor %}
{% endif %}

View File

@@ -205,6 +205,7 @@ class Auth(object):
)
user_id, app_service = yield self._get_appservice_user_id(request)
if user_id:
request.authenticated_entity = user_id
@@ -255,39 +256,40 @@ class Auth(object):
errcode=Codes.MISSING_TOKEN
)
@defer.inlineCallbacks
def _get_appservice_user_id(self, request):
app_service = self.store.get_app_service_by_token(
self.get_access_token_from_request(
request, self.TOKEN_NOT_FOUND_HTTP_STATUS
)
)
if app_service is None:
defer.returnValue((None, None))
return(None, None)
if app_service.ip_range_whitelist:
ip_address = IPAddress(self.hs.get_ip_from_request(request))
if ip_address not in app_service.ip_range_whitelist:
defer.returnValue((None, None))
return(None, None)
if b"user_id" not in request.args:
defer.returnValue((app_service.sender, app_service))
return(app_service.sender, app_service)
user_id = request.args[b"user_id"][0].decode('utf8')
if app_service.sender == user_id:
defer.returnValue((app_service.sender, app_service))
return(app_service.sender, app_service)
if not app_service.is_interested_in_user(user_id):
raise AuthError(
403,
"Application service cannot masquerade as this user."
)
if not (yield self.store.get_user_by_id(user_id)):
raise AuthError(
403,
"Application service has not registered this user"
)
defer.returnValue((user_id, app_service))
# Let ASes manipulate nonexistent users (e.g. to shadow-register them)
# if not (yield self.store.get_user_by_id(user_id)):
# raise AuthError(
# 403,
# "Application service has not registered this user"
# )
return(user_id, app_service)
@defer.inlineCallbacks
def get_user_by_access_token(self, token, rights="access"):
@@ -520,24 +522,15 @@ class Auth(object):
defer.returnValue(user_info)
def get_appservice_by_req(self, request):
try:
token = self.get_access_token_from_request(
request, self.TOKEN_NOT_FOUND_HTTP_STATUS
)
service = self.store.get_app_service_by_token(token)
if not service:
logger.warn("Unrecognised appservice access token.")
raise AuthError(
self.TOKEN_NOT_FOUND_HTTP_STATUS,
"Unrecognised access token.",
errcode=Codes.UNKNOWN_TOKEN
)
request.authenticated_entity = service.sender
return defer.succeed(service)
except KeyError:
(user_id, app_service) = self._get_appservice_user_id(request)
if not app_service:
raise AuthError(
self.TOKEN_NOT_FOUND_HTTP_STATUS, "Missing access token."
self.TOKEN_NOT_FOUND_HTTP_STATUS,
"Unrecognised access token.",
errcode=Codes.UNKNOWN_TOKEN,
)
request.authenticated_entity = app_service.sender
return app_service
def is_server_admin(self, user):
""" Check if the given user is a local server admin.

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright 2014-2016 OpenMarket Ltd
# Copyright 2017 Vector Creations Ltd
# Copyright 2018 New Vector Ltd.
# Copyright 2018 New Vector Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -75,6 +75,7 @@ class EventTypes(object):
RoomAvatar = "m.room.avatar"
RoomEncryption = "m.room.encryption"
GuestAccess = "m.room.guest_access"
Encryption = "m.room.encryption"
# These are used for validation
Message = "m.room.message"

View File

@@ -265,7 +265,7 @@ class ApplicationService(object):
def is_exclusive_room(self, room_id):
return self._is_exclusive(ApplicationService.NS_ROOMS, room_id)
def get_exlusive_user_regexes(self):
def get_exclusive_user_regexes(self):
"""Get the list of regexes used to determine if a user is exclusively
registered by the AS
"""

View File

@@ -33,7 +33,18 @@ class RegistrationConfig(Config):
self.registrations_require_3pid = config.get("registrations_require_3pid", [])
self.allowed_local_3pids = config.get("allowed_local_3pids", [])
self.check_is_for_allowed_local_3pids = config.get(
"check_is_for_allowed_local_3pids", None
)
self.allow_invited_3pids = config.get("allow_invited_3pids", False)
self.disable_3pid_changes = config.get("disable_3pid_changes", False)
self.registration_shared_secret = config.get("registration_shared_secret")
self.register_mxid_from_3pid = config.get("register_mxid_from_3pid")
self.register_just_use_email_for_display_name = config.get(
"register_just_use_email_for_display_name", False,
)
self.bcrypt_rounds = config.get("bcrypt_rounds", 12)
self.trusted_third_party_id_servers = config.get(
@@ -53,6 +64,16 @@ class RegistrationConfig(Config):
raise ConfigError('Invalid auto_join_rooms entry %s' % (room_alias,))
self.autocreate_auto_join_rooms = config.get("autocreate_auto_join_rooms", True)
self.disable_set_displayname = config.get("disable_set_displayname", False)
self.disable_set_avatar_url = config.get("disable_set_avatar_url", False)
self.replicate_user_profiles_to = config.get("replicate_user_profiles_to", [])
if not isinstance(self.replicate_user_profiles_to, list):
self.replicate_user_profiles_to = [self.replicate_user_profiles_to, ]
self.shadow_server = config.get("shadow_server", None)
self.rewrite_identity_server_urls = config.get("rewrite_identity_server_urls", {})
self.disable_msisdn_registration = (
config.get("disable_msisdn_registration", False)
)
@@ -86,9 +107,32 @@ class RegistrationConfig(Config):
#
#disable_msisdn_registration: true
# Derive the user's matrix ID from a type of 3PID used when registering.
# This overrides any matrix ID the user proposes when calling /register
# The 3PID type should be present in registrations_require_3pid to avoid
# users failing to register if they don't specify the right kind of 3pid.
#
#register_mxid_from_3pid: email
# Uncomment to set the display name of new users to their email address,
# rather than using the default heuristic.
#
#register_just_use_email_for_display_name: true
# Mandate that users are only allowed to associate certain formats of
# 3PIDs with accounts on this server.
#
# Use an Identity Server to establish which 3PIDs are allowed to register?
# Overrides allowed_local_3pids below.
#
#check_is_for_allowed_local_3pids: matrix.org
#
# If you are using an IS you can also check whether that IS registers
# pending invites for the given 3PID (and then allow it to sign up on
# the platform):
#
#allow_invited_3pids: False
#
#allowed_local_3pids:
# - medium: email
# pattern: '.*@matrix\\.org'
@@ -97,6 +141,11 @@ class RegistrationConfig(Config):
# - medium: msisdn
# pattern: '\\+44'
# If true, stop users from trying to change the 3PIDs associated with
# their accounts.
#
#disable_3pid_changes: False
# If set, allows registration of standard or admin accounts by anyone who
# has the shared secret, even if registration is otherwise disabled.
#
@@ -134,6 +183,30 @@ class RegistrationConfig(Config):
# - matrix.org
# - vector.im
# If enabled, user IDs, display names and avatar URLs will be replicated
# to this server whenever they change.
# This is an experimental API currently implemented by sydent to support
# cross-homeserver user directories.
#
#replicate_user_profiles_to: example.com
# If specified, attempt to replay registrations, profile changes & 3pid
# bindings on the given target homeserver via the AS API. The HS is authed
# via a given AS token.
#
#shadow_server:
# hs_url: https://shadow.example.com
# hs: shadow.example.com
# as_token: 12u394refgbdhivsia
# If enabled, don't let users set their own display names/avatars
# other than for the very first time (unless they are a server admin).
# Useful when provisioning users based on the contents of a 3rd party
# directory and to avoid ambiguities.
#
#disable_set_displayname: False
#disable_set_avatar_url: False
# Users who register on this homeserver will automatically be joined
# to these rooms
#

View File

@@ -24,6 +24,7 @@ class UserDirectoryConfig(Config):
def read_config(self, config):
self.user_directory_search_enabled = True
self.user_directory_search_all_users = False
self.user_directory_defer_to_id_server = None
user_directory_config = config.get("user_directory", None)
if user_directory_config:
self.user_directory_search_enabled = (
@@ -32,6 +33,9 @@ class UserDirectoryConfig(Config):
self.user_directory_search_all_users = (
user_directory_config.get("search_all_users", False)
)
self.user_directory_defer_to_id_server = (
user_directory_config.get("defer_to_id_server", None)
)
def default_config(self, config_dir_path, server_name, **kwargs):
return """
@@ -50,4 +54,9 @@ class UserDirectoryConfig(Config):
#user_directory:
# enabled: true
# search_all_users: false
#
# # If this is set, user search will be delegated to this ID server instead
# # of synapse performing the search itself.
# # This is an experimental API.
# defer_to_id_server: https://id.example.com
"""

View File

@@ -46,13 +46,23 @@ class SpamChecker(object):
return self.spam_checker.check_event_for_spam(event)
def user_may_invite(self, inviter_userid, invitee_userid, room_id):
def user_may_invite(self, inviter_userid, invitee_userid, third_party_invite,
room_id, new_room):
"""Checks if a given user may send an invite
If this method returns false, the invite will be rejected.
Args:
userid (string): The sender's user ID
inviter_userid (str)
invitee_userid (str|None): The user ID of the invitee. Is None
if this is a third party invite and the 3PID is not bound to a
user ID.
third_party_invite (dict|None): If a third party invite then is a
dict containing the medium and address of the invitee.
room_id (str)
new_room (bool): Wether the user is being invited to the room as
part of a room creation, if so the invitee would have been
included in the call to `user_may_create_room`.
Returns:
bool: True if the user may send an invite, otherwise False
@@ -60,15 +70,24 @@ class SpamChecker(object):
if self.spam_checker is None:
return True
return self.spam_checker.user_may_invite(inviter_userid, invitee_userid, room_id)
return self.spam_checker.user_may_invite(
inviter_userid, invitee_userid, third_party_invite, room_id, new_room,
)
def user_may_create_room(self, userid):
def user_may_create_room(self, userid, invite_list, third_party_invite_list,
cloning):
"""Checks if a given user may create a room
If this method returns false, the creation request will be rejected.
Args:
userid (string): The sender's user ID
invite_list (list[str]): List of user IDs that would be invited to
the new room.
third_party_invite_list (list[dict]): List of third party invites
for the new room.
cloning (bool): Whether the user is cloning an existing room, e.g.
upgrading a room.
Returns:
bool: True if the user may create a room, otherwise False
@@ -76,7 +95,9 @@ class SpamChecker(object):
if self.spam_checker is None:
return True
return self.spam_checker.user_may_create_room(userid)
return self.spam_checker.user_may_create_room(
userid, invite_list, third_party_invite_list, cloning,
)
def user_may_create_room_alias(self, userid, room_alias):
"""Checks if a given user may create a room alias
@@ -111,3 +132,21 @@ class SpamChecker(object):
return True
return self.spam_checker.user_may_publish_room(userid, room_id)
def user_may_join_room(self, userid, room_id, is_invited):
"""Checks if a given users is allowed to join a room.
Is not called when the user creates a room.
Args:
userid (str)
room_id (str)
is_invited (bool): Whether the user is invited into the room
Returns:
bool: Whether the user may join the room
"""
if self.spam_checker is None:
return True
return self.spam_checker.user_may_join_room(userid, room_id, is_invited)

View File

@@ -33,6 +33,7 @@ class DeactivateAccountHandler(BaseHandler):
self._device_handler = hs.get_device_handler()
self._room_member_handler = hs.get_room_member_handler()
self._identity_handler = hs.get_handlers().identity_handler
self._profile_handler = hs.get_profile_handler()
self.user_directory_handler = hs.get_user_directory_handler()
# Flag that indicates whether the process to part users from rooms is running
@@ -94,6 +95,9 @@ class DeactivateAccountHandler(BaseHandler):
yield self.store.user_set_password_hash(user_id, None)
user = UserID.from_string(user_id)
yield self._profile_handler.set_active(user, False, False)
# Add the user to a table of users pending deactivation (ie.
# removal from all the rooms they're a member of)
yield self.store.add_user_pending_deactivation(user_id)

View File

@@ -568,6 +568,12 @@ class DeviceListEduUpdater(object):
stream_id = result["stream_id"]
devices = result["devices"]
for device in devices:
logger.debug(
"Handling resync update %r/%r, ID: %r",
user_id, device["device_id"], stream_id,
)
# If the remote server has more than ~1000 devices for this user
# we assume that something is going horribly wrong (e.g. a bot
# that logs in and creates a new device every time it tries to

View File

@@ -1346,7 +1346,8 @@ class FederationHandler(BaseHandler):
raise SynapseError(403, "This server does not accept room invites")
if not self.spam_checker.user_may_invite(
event.sender, event.state_key, event.room_id,
event.sender, event.state_key, None,
room_id=event.room_id, new_room=False,
):
raise SynapseError(
403, "This user is not permitted to send invites to this server/user"

View File

@@ -47,6 +47,7 @@ class IdentityHandler(BaseHandler):
self.trust_any_id_server_just_for_testing_do_not_use = (
hs.config.use_insecure_ssl_client_just_for_testing_do_not_use
)
self.rewrite_identity_server_urls = hs.config.rewrite_identity_server_urls
def _should_trust_id_server(self, id_server):
if id_server not in self.trusted_id_servers:
@@ -84,7 +85,10 @@ class IdentityHandler(BaseHandler):
'credentials', id_server
)
defer.returnValue(None)
# if we have a rewrite rule set for the identity server,
# apply it now.
if id_server in self.rewrite_identity_server_urls:
id_server = self.rewrite_identity_server_urls[id_server]
try:
data = yield self.http_client.get_json(
"https://%s%s" % (
@@ -119,7 +123,10 @@ class IdentityHandler(BaseHandler):
client_secret = creds['clientSecret']
else:
raise SynapseError(400, "No client_secret in creds")
# if we have a rewrite rule set for the identity server,
# apply it now.
if id_server in self.rewrite_identity_server_urls:
id_server = self.rewrite_identity_server_urls[id_server]
try:
data = yield self.http_client.post_urlencoded_get_json(
"https://%s%s" % (
@@ -162,7 +169,6 @@ class IdentityHandler(BaseHandler):
# deletion request to.
id_server = next(iter(self.trusted_id_servers))
url = "https://%s/_matrix/identity/api/v1/3pid/unbind" % (id_server,)
content = {
"mxid": mxid,
"threepid": threepid,
@@ -182,6 +188,16 @@ class IdentityHandler(BaseHandler):
b"Authorization": auth_headers,
}
# if we have a rewrite rule set for the identity server,
# apply it now.
#
# Note that destination_is has to be the real id_server, not
# the server we connect to.
if id_server in self.rewrite_identity_server_urls:
id_server = self.rewrite_identity_server_urls[id_server]
url = "https://%s/_matrix/identity/api/v1/3pid/unbind" % (id_server,)
try:
yield self.http_client.post_json_get_json(
url,
@@ -213,7 +229,10 @@ class IdentityHandler(BaseHandler):
'send_attempt': send_attempt,
}
params.update(kwargs)
# if we have a rewrite rule set for the identity server,
# apply it now.
if id_server in self.rewrite_identity_server_urls:
id_server = self.rewrite_identity_server_urls[id_server]
try:
data = yield self.http_client.post_json_get_json(
"https://%s%s" % (
@@ -245,7 +264,10 @@ class IdentityHandler(BaseHandler):
'send_attempt': send_attempt,
}
params.update(kwargs)
# if we have a rewrite rule set for the identity server,
# apply it now.
if id_server in self.rewrite_identity_server_urls:
id_server = self.rewrite_identity_server_urls[id_server]
try:
data = yield self.http_client.post_json_get_json(
"https://%s%s" % (

View File

@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2014-2016 OpenMarket Ltd
# Copyright 2018 New Vector Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,7 +16,11 @@
import logging
from twisted.internet import defer
from six.moves import range
from signedjson.sign import sign_json
from twisted.internet import defer, reactor
from synapse.api.errors import (
AuthError,
@@ -26,6 +31,7 @@ from synapse.api.errors import (
)
from synapse.metrics.background_process_metrics import run_as_background_process
from synapse.types import UserID, get_domain_from_id
from synapse.util.logcontext import run_in_background
from ._base import BaseHandler
@@ -40,6 +46,8 @@ class BaseProfileHandler(BaseHandler):
subclass MasterProfileHandler
"""
PROFILE_REPLICATE_INTERVAL = 2 * 60 * 1000
def __init__(self, hs):
super(BaseProfileHandler, self).__init__(hs)
@@ -50,6 +58,84 @@ class BaseProfileHandler(BaseHandler):
self.user_directory_handler = hs.get_user_directory_handler()
self.http_client = hs.get_simple_http_client()
if hs.config.worker_app is None:
self.clock.looping_call(
self._start_update_remote_profile_cache, self.PROFILE_UPDATE_MS,
)
if len(self.hs.config.replicate_user_profiles_to) > 0:
reactor.callWhenRunning(self._assign_profile_replication_batches)
reactor.callWhenRunning(self._replicate_profiles)
# Add a looping call to replicate_profiles: this handles retries
# if the replication is unsuccessful when the user updated their
# profile.
self.clock.looping_call(
self._replicate_profiles, self.PROFILE_REPLICATE_INTERVAL
)
@defer.inlineCallbacks
def _assign_profile_replication_batches(self):
"""If no profile replication has been done yet, allocate replication batch
numbers to each profile to start the replication process.
"""
logger.info("Assigning profile batch numbers...")
total = 0
while True:
assigned = yield self.store.assign_profile_batch()
total += assigned
if assigned == 0:
break
logger.info("Assigned %d profile batch numbers", total)
@defer.inlineCallbacks
def _replicate_profiles(self):
"""If any profile data has been updated and not pushed to the replication targets,
replicate it.
"""
host_batches = yield self.store.get_replication_hosts()
latest_batch = yield self.store.get_latest_profile_replication_batch_number()
if latest_batch is None:
latest_batch = -1
for repl_host in self.hs.config.replicate_user_profiles_to:
if repl_host not in host_batches:
host_batches[repl_host] = -1
try:
for i in range(host_batches[repl_host] + 1, latest_batch + 1):
yield self._replicate_host_profile_batch(repl_host, i)
except Exception:
logger.exception(
"Exception while replicating to %s: aborting for now", repl_host,
)
@defer.inlineCallbacks
def _replicate_host_profile_batch(self, host, batchnum):
logger.info("Replicating profile batch %d to %s", batchnum, host)
batch_rows = yield self.store.get_profile_batch(batchnum)
batch = {
UserID(r["user_id"], self.hs.hostname).to_string(): ({
"display_name": r["displayname"],
"avatar_url": r["avatar_url"],
} if r["active"] else None) for r in batch_rows
}
url = "https://%s/_matrix/identity/api/v1/replicate_profiles" % (host,)
body = {
"batchnum": batchnum,
"batch": batch,
"origin_server": self.hs.hostname,
}
signed_body = sign_json(body, self.hs.hostname, self.hs.config.signing_key[0])
try:
yield self.http_client.post_json_get_json(url, signed_body)
yield self.store.update_replication_batch_for_host(host, batchnum)
logger.info("Sucessfully replicated profile batch %d to %s", batchnum, host)
except Exception:
# This will get retried when the looping call next comes around
logger.exception("Failed to replicate profile batch %d to %s", batchnum, host)
raise
@defer.inlineCallbacks
def get_profile(self, user_id):
target_user = UserID.from_string(user_id)
@@ -147,19 +233,30 @@ class BaseProfileHandler(BaseHandler):
@defer.inlineCallbacks
def set_displayname(self, target_user, requester, new_displayname, by_admin=False):
"""target_user is the user whose displayname is to be changed;
auth_user is the user attempting to make this change."""
"""target_user is the UserID whose displayname is to be changed;
requester is the authenticated user attempting to make this change."""
if not self.hs.is_mine(target_user):
raise SynapseError(400, "User is not hosted on this Home Server")
if not by_admin and target_user != requester.user:
if not by_admin and requester and target_user != requester.user:
raise AuthError(400, "Cannot set another user's displayname")
if not by_admin and self.hs.config.disable_set_displayname:
profile = yield self.store.get_profileinfo(target_user.localpart)
if profile.display_name:
raise SynapseError(400, "Changing displayname is disabled on this server")
if new_displayname == '':
new_displayname = None
if len(self.hs.config.replicate_user_profiles_to) > 0:
cur_batchnum = yield self.store.get_latest_profile_replication_batch_number()
new_batchnum = 0 if cur_batchnum is None else cur_batchnum + 1
else:
new_batchnum = None
yield self.store.set_profile_displayname(
target_user.localpart, new_displayname
target_user.localpart, new_displayname, new_batchnum
)
if self.hs.config.user_directory_search_all_users:
@@ -168,7 +265,37 @@ class BaseProfileHandler(BaseHandler):
target_user.to_string(), profile
)
yield self._update_join_states(requester, target_user)
if requester:
yield self._update_join_states(requester, target_user)
# start a profile replication push
run_in_background(self._replicate_profiles)
@defer.inlineCallbacks
def set_active(self, target_user, active, hide):
"""
Sets the 'active' flag on a user profile. If set to false, the user
account is considered deactivated or hidden.
If 'hide' is true, then we interpret active=False as a request to try to
hide the user rather than deactivating it. This means withholding the
profile from replication (and mark it as inactive) rather than clearing
the profile from the HS DB. Note that unlike set_displayname and
set_avatar_url, this does *not* perform authorization checks! This is
because the only place it's used currently is in account deactivation
where we've already done these checks anyway.
"""
if len(self.hs.config.replicate_user_profiles_to) > 0:
cur_batchnum = yield self.store.get_latest_profile_replication_batch_number()
new_batchnum = 0 if cur_batchnum is None else cur_batchnum + 1
else:
new_batchnum = None
yield self.store.set_profile_active(
target_user.localpart, active, hide, new_batchnum
)
# start a profile replication push
run_in_background(self._replicate_profiles)
@defer.inlineCallbacks
def get_avatar_url(self, target_user):
@@ -210,8 +337,19 @@ class BaseProfileHandler(BaseHandler):
if not by_admin and target_user != requester.user:
raise AuthError(400, "Cannot set another user's avatar_url")
if not by_admin and self.hs.config.disable_set_avatar_url:
profile = yield self.store.get_profileinfo(target_user.localpart)
if profile.avatar_url:
raise SynapseError(400, "Changing avatar url is disabled on this server")
if len(self.hs.config.replicate_user_profiles_to) > 0:
cur_batchnum = yield self.store.get_latest_profile_replication_batch_number()
new_batchnum = 0 if cur_batchnum is None else cur_batchnum + 1
else:
new_batchnum = None
yield self.store.set_profile_avatar_url(
target_user.localpart, new_avatar_url
target_user.localpart, new_avatar_url, new_batchnum,
)
if self.hs.config.user_directory_search_all_users:
@@ -222,6 +360,9 @@ class BaseProfileHandler(BaseHandler):
yield self._update_join_states(requester, target_user)
# start a profile replication push
run_in_background(self._replicate_profiles)
@defer.inlineCallbacks
def on_profile_query(self, args):
user = UserID.from_string(args["user_id"])

View File

@@ -61,6 +61,7 @@ class RegistrationHandler(BaseHandler):
self.profile_handler = hs.get_profile_handler()
self.user_directory_handler = hs.get_user_directory_handler()
self.captcha_client = CaptchaServerHttpClient(hs)
self.http_client = hs.get_simple_http_client()
self.identity_handler = self.hs.get_handlers().identity_handler
self.ratelimiter = hs.get_registration_ratelimiter()
@@ -223,6 +224,11 @@ class RegistrationHandler(BaseHandler):
address=address,
)
if default_display_name:
yield self.profile_handler.set_displayname(
user, None, default_display_name, by_admin=True,
)
if self.hs.config.user_directory_search_all_users:
profile = yield self.store.get_profileinfo(localpart)
yield self.user_directory_handler.handle_local_profile_change(
@@ -252,6 +258,11 @@ class RegistrationHandler(BaseHandler):
create_profile_with_displayname=default_display_name,
address=address,
)
yield self.profile_handler.set_displayname(
user, None, default_display_name, by_admin=True,
)
except SynapseError:
# if user id is taken, just generate another
user = None
@@ -330,7 +341,9 @@ class RegistrationHandler(BaseHandler):
yield self._auto_join_rooms(user_id)
@defer.inlineCallbacks
def appservice_register(self, user_localpart, as_token):
def appservice_register(self, user_localpart, as_token, password, display_name):
# FIXME: this should be factored out and merged with normal register()
user = UserID(user_localpart, self.hs.hostname)
user_id = user.to_string()
service = self.store.get_app_service_by_token(as_token)
@@ -348,12 +361,29 @@ class RegistrationHandler(BaseHandler):
user_id, allowed_appservice=service
)
password_hash = ""
if password:
password_hash = yield self.auth_handler().hash(password)
display_name = display_name or user.localpart
yield self.register_with_store(
user_id=user_id,
password_hash="",
password_hash=password_hash,
appservice_id=service_id,
create_profile_with_displayname=user.localpart,
create_profile_with_displayname=display_name,
)
yield self.profile_handler.set_displayname(
user, None, display_name, by_admin=True,
)
if self.hs.config.user_directory_search_all_users:
profile = yield self.store.get_profileinfo(user_localpart)
yield self.user_directory_handler.handle_local_profile_change(
user_id, profile
)
defer.returnValue(user_id)
@defer.inlineCallbacks
@@ -379,6 +409,39 @@ class RegistrationHandler(BaseHandler):
else:
logger.info("Valid captcha entered from %s", ip)
@defer.inlineCallbacks
def register_saml2(self, localpart):
"""
Registers email_id as SAML2 Based Auth.
"""
if types.contains_invalid_mxid_characters(localpart):
raise SynapseError(
400,
"User ID can only contain characters a-z, 0-9, or '=_-./'",
)
yield self.auth.check_auth_blocking()
user = UserID(localpart, self.hs.hostname)
user_id = user.to_string()
yield self.check_user_id_not_appservice_exclusive(user_id)
token = self.macaroon_gen.generate_access_token(user_id)
try:
yield self.register_with_store(
user_id=user_id,
token=token,
password_hash=None,
create_profile_with_displayname=user.localpart,
)
yield self.profile_handler.set_displayname(
user, None, user.localpart, by_admin=True,
)
except Exception as e:
yield self.store.add_access_token_to_user(user_id, token)
# Ignore Registration errors
logger.exception(e)
defer.returnValue((user_id, token))
@defer.inlineCallbacks
def register_email(self, threepidCreds):
"""
@@ -401,7 +464,9 @@ class RegistrationHandler(BaseHandler):
logger.info("got threepid with medium '%s' and address '%s'",
threepid['medium'], threepid['address'])
if not check_3pid_allowed(self.hs, threepid['medium'], threepid['address']):
if not (
yield check_3pid_allowed(self.hs, threepid['medium'], threepid['address'])
):
raise RegistrationError(
403, "Third party identifier is not allowed"
)
@@ -442,6 +507,39 @@ class RegistrationHandler(BaseHandler):
errcode=Codes.EXCLUSIVE
)
@defer.inlineCallbacks
def shadow_register(self, localpart, display_name, auth_result, params):
"""Invokes the current registration on another server, using
shared secret registration, passing in any auth_results from
other registration UI auth flows (e.g. validated 3pids)
Useful for setting up shadow/backup accounts on a parallel deployment.
"""
# TODO: retries
shadow_hs_url = self.hs.config.shadow_server.get("hs_url")
as_token = self.hs.config.shadow_server.get("as_token")
yield self.http_client.post_json_get_json(
"%s/_matrix/client/r0/register?access_token=%s" % (
shadow_hs_url, as_token,
),
{
# XXX: auth_result is an unspecified extension for shadow registration
'auth_result': auth_result,
# XXX: another unspecified extension for shadow registration to ensure
# that the displayname is correctly set by the masters erver
'display_name': display_name,
'username': localpart,
'password': params.get("password"),
'bind_email': params.get("bind_email"),
'bind_msisdn': params.get("bind_msisdn"),
'device_id': params.get("device_id"),
'initial_device_display_name': params.get("initial_device_display_name"),
'inhibit_login': False,
'access_token': as_token,
}
)
@defer.inlineCallbacks
def _generate_user_id(self, reseed=False):
if reseed or self._next_generated_user_id is None:
@@ -528,18 +626,16 @@ class RegistrationHandler(BaseHandler):
user_id=user_id,
token=token,
password_hash=password_hash,
create_profile_with_displayname=user.localpart,
create_profile_with_displayname=displayname or user.localpart,
)
if displayname is not None:
yield self.profile_handler.set_displayname(
user, None, displayname or user.localpart, by_admin=True,
)
else:
yield self._auth_handler.delete_access_tokens_for_user(user_id)
yield self.store.add_access_token_to_user(user_id=user_id, token=token)
if displayname is not None:
logger.info("setting user display name: %s -> %s", user_id, displayname)
yield self.profile_handler.set_displayname(
user, requester, displayname, by_admin=True,
)
defer.returnValue((user_id, token))
@defer.inlineCallbacks

View File

@@ -54,12 +54,14 @@ class RoomCreationHandler(BaseHandler):
"history_visibility": "shared",
"original_invitees_have_ops": False,
"guest_can_join": True,
"encryption_alg": "m.megolm.v1.aes-sha2",
},
RoomCreationPreset.TRUSTED_PRIVATE_CHAT: {
"join_rules": JoinRules.INVITE,
"history_visibility": "shared",
"original_invitees_have_ops": True,
"guest_can_join": True,
"encryption_alg": "m.megolm.v1.aes-sha2",
},
RoomCreationPreset.PUBLIC_CHAT: {
"join_rules": JoinRules.PUBLIC,
@@ -79,6 +81,8 @@ class RoomCreationHandler(BaseHandler):
# linearizer to stop two upgrades happening at once
self._upgrade_linearizer = Linearizer("room_upgrade_linearizer")
self._server_notices_mxid = hs.config.server_notices_mxid
@defer.inlineCallbacks
def upgrade_room(self, requester, old_room_id, new_version):
"""Replace a room with a new room with a different version
@@ -252,7 +256,22 @@ class RoomCreationHandler(BaseHandler):
"""
user_id = requester.user.to_string()
if not self.spam_checker.user_may_create_room(user_id):
if (self._server_notices_mxid is not None and
requester.user.to_string() == self._server_notices_mxid):
# allow the server notices mxid to create rooms
is_requester_admin = True
else:
is_requester_admin = yield self.auth.is_server_admin(
requester.user,
)
if not is_requester_admin and not self.spam_checker.user_may_create_room(
user_id,
invite_list=[],
third_party_invite_list=[],
cloning=True,
):
raise SynapseError(403, "You are not permitted to create rooms")
creation_content = {
@@ -473,7 +492,24 @@ class RoomCreationHandler(BaseHandler):
yield self.auth.check_auth_blocking(user_id)
if not self.spam_checker.user_may_create_room(user_id):
invite_list = config.get("invite", [])
invite_3pid_list = config.get("invite_3pid", [])
if (self._server_notices_mxid is not None and
requester.user.to_string() == self._server_notices_mxid):
# allow the server notices mxid to create rooms
is_requester_admin = True
else:
is_requester_admin = yield self.auth.is_server_admin(
requester.user,
)
if not is_requester_admin and not self.spam_checker.user_may_create_room(
user_id,
invite_list=invite_list,
third_party_invite_list=invite_3pid_list,
cloning=False,
):
raise SynapseError(403, "You are not permitted to create rooms")
if ratelimit:
@@ -516,7 +552,6 @@ class RoomCreationHandler(BaseHandler):
else:
room_alias = None
invite_list = config.get("invite", [])
for i in invite_list:
try:
UserID.from_string(i)
@@ -527,8 +562,6 @@ class RoomCreationHandler(BaseHandler):
requester,
)
invite_3pid_list = config.get("invite_3pid", [])
visibility = config.get("visibility", None)
is_public = visibility == "public"
@@ -613,6 +646,7 @@ class RoomCreationHandler(BaseHandler):
"invite",
ratelimit=False,
content=content,
new_room=True,
)
for invite_3pid in invite_3pid_list:
@@ -627,6 +661,7 @@ class RoomCreationHandler(BaseHandler):
id_server,
requester,
txn_id=None,
new_room=True,
)
result = {"room_id": room_id}
@@ -697,6 +732,7 @@ class RoomCreationHandler(BaseHandler):
"join",
ratelimit=False,
content=creator_join_profile,
new_room=True,
)
# We treat the power levels override specially as this needs to be one
@@ -772,6 +808,15 @@ class RoomCreationHandler(BaseHandler):
content=content,
)
if "encryption_alg" in config:
yield send(
etype=EventTypes.Encryption,
state_key="",
content={
'algorithm': config["encryption_alg"],
}
)
@defer.inlineCallbacks
def _generate_room_id(self, creator_id, is_public):
# autogen room IDs and try to create it. We may clash, so just

View File

@@ -70,6 +70,7 @@ class RoomMemberHandler(object):
self.clock = hs.get_clock()
self.spam_checker = hs.get_spam_checker()
self._server_notices_mxid = self.config.server_notices_mxid
self.rewrite_identity_server_urls = self.config.rewrite_identity_server_urls
@abc.abstractmethod
def _remote_join(self, requester, remote_room_hosts, room_id, user, content):
@@ -307,8 +308,31 @@ class RoomMemberHandler(object):
third_party_signed=None,
ratelimit=True,
content=None,
new_room=False,
require_consent=True,
):
"""Update a users membership in a room
Args:
requester (Requester)
target (UserID)
room_id (str)
action (str): The "action" the requester is performing against the
target. One of join/leave/kick/ban/invite/unban.
txn_id (str|None): The transaction ID associated with the request,
or None not provided.
remote_room_hosts (list[str]|None): List of remote servers to try
and join via if server isn't already in the room.
third_party_signed (dict|None): The signed object for third party
invites.
ratelimit (bool): Whether to apply ratelimiting to this request.
content (dict|None): Fields to include in the new events content.
new_room (bool): Whether these membership changes are happening
as part of a room creation (e.g. initial joins and invites)
Returns:
Deferred[FrozenEvent]
"""
key = (room_id,)
with (yield self.member_linearizer.queue(key)):
@@ -322,6 +346,7 @@ class RoomMemberHandler(object):
third_party_signed=third_party_signed,
ratelimit=ratelimit,
content=content,
new_room=new_room,
require_consent=require_consent,
)
@@ -339,6 +364,7 @@ class RoomMemberHandler(object):
third_party_signed=None,
ratelimit=True,
content=None,
new_room=False,
require_consent=True,
):
content_specified = bool(content)
@@ -400,7 +426,10 @@ class RoomMemberHandler(object):
block_invite = True
if not self.spam_checker.user_may_invite(
requester.user.to_string(), target.to_string(), room_id,
requester.user.to_string(), target.to_string(),
third_party_invite=None,
room_id=room_id,
new_room=new_room,
):
logger.info("Blocking invite due to spam checker")
block_invite = True
@@ -470,8 +499,29 @@ class RoomMemberHandler(object):
# so don't really fit into the general auth process.
raise AuthError(403, "Guest access not allowed")
if (self._server_notices_mxid is not None and
requester.user.to_string() == self._server_notices_mxid):
# allow the server notices mxid to join rooms
is_requester_admin = True
else:
is_requester_admin = yield self.auth.is_server_admin(
requester.user,
)
inviter = yield self._get_inviter(target.to_string(), room_id)
if not is_requester_admin:
# We assume that if the spam checker allowed the user to create
# a room then they're allowed to join it.
if not new_room and not self.spam_checker.user_may_join_room(
target.to_string(), room_id,
is_invited=inviter is not None,
):
raise SynapseError(
403, "Not allowed to join this room",
)
if not is_host_in_room:
inviter = yield self._get_inviter(target.to_string(), room_id)
if inviter and not self.hs.is_mine(inviter):
remote_room_hosts.append(inviter.domain)
@@ -681,7 +731,8 @@ class RoomMemberHandler(object):
address,
id_server,
requester,
txn_id
txn_id,
new_room=False,
):
if self.config.block_non_admin_invites:
is_requester_admin = yield self.auth.is_server_admin(
@@ -697,6 +748,20 @@ class RoomMemberHandler(object):
id_server, medium, address
)
if not self.spam_checker.user_may_invite(
requester.user.to_string(), invitee,
third_party_invite={
"medium": medium,
"address": address,
},
room_id=room_id,
new_room=new_room,
):
logger.info("Blocking invite due to spam checker")
raise SynapseError(
403, "Invites have been disabled on this server",
)
if invitee:
yield self.update_membership(
requester,
@@ -716,6 +781,20 @@ class RoomMemberHandler(object):
txn_id=txn_id
)
def _get_id_server_target(self, id_server):
"""Looks up an id_server's actual http endpoint
Args:
id_server (str): the server name to lookup.
Returns:
the http endpoint to connect to.
"""
if id_server in self.rewrite_identity_server_urls:
return self.rewrite_identity_server_urls[id_server]
return id_server
@defer.inlineCallbacks
def _lookup_3pid(self, id_server, medium, address):
"""Looks up a 3pid in the passed identity server.
@@ -730,8 +809,9 @@ class RoomMemberHandler(object):
str: the matrix ID of the 3pid, or None if it is not recognized.
"""
try:
target = self._get_id_server_target(id_server)
data = yield self.simple_http_client.get_json(
"%s%s/_matrix/identity/api/v1/lookup" % (id_server_scheme, id_server,),
"%s%s/_matrix/identity/api/v1/lookup" % (id_server_scheme, target,),
{
"medium": medium,
"address": address,
@@ -753,9 +833,10 @@ class RoomMemberHandler(object):
if server_hostname not in data["signatures"]:
raise AuthError(401, "No signature from server %s" % (server_hostname,))
for key_name, signature in data["signatures"][server_hostname].items():
target = self._get_id_server_target(server_hostname)
key_data = yield self.simple_http_client.get_json(
"%s%s/_matrix/identity/api/v1/pubkey/%s" %
(id_server_scheme, server_hostname, key_name,),
(id_server_scheme, target, key_name,),
)
if "public_key" not in key_data:
raise AuthError(401, "No public key named %s from %s" %
@@ -892,8 +973,9 @@ class RoomMemberHandler(object):
user.
"""
target = self._get_id_server_target(id_server)
is_url = "%s%s/_matrix/identity/api/v1/store-invite" % (
id_server_scheme, id_server,
id_server_scheme, target,
)
invite_config = {
@@ -933,7 +1015,7 @@ class RoomMemberHandler(object):
fallback_public_key = {
"public_key": data["public_key"],
"key_validity_url": "%s%s/_matrix/identity/api/v1/pubkey/isvalid" % (
id_server_scheme, id_server,
id_server_scheme, target,
),
}
else:

View File

@@ -934,7 +934,7 @@ class SyncHandler(object):
res = yield self._generate_sync_entry_for_rooms(
sync_result_builder, account_data_by_room
)
newly_joined_rooms, newly_joined_users, _, _ = res
newly_joined_rooms, newly_joined_or_invited_users, _, _ = res
_, _, newly_left_rooms, newly_left_users = res
block_all_presence_data = (
@@ -943,7 +943,7 @@ class SyncHandler(object):
)
if self.hs_config.use_presence and not block_all_presence_data:
yield self._generate_sync_entry_for_presence(
sync_result_builder, newly_joined_rooms, newly_joined_users
sync_result_builder, newly_joined_rooms, newly_joined_or_invited_users
)
yield self._generate_sync_entry_for_to_device(sync_result_builder)
@@ -951,7 +951,7 @@ class SyncHandler(object):
device_lists = yield self._generate_sync_entry_for_device_list(
sync_result_builder,
newly_joined_rooms=newly_joined_rooms,
newly_joined_users=newly_joined_users,
newly_joined_or_invited_users=newly_joined_or_invited_users,
newly_left_rooms=newly_left_rooms,
newly_left_users=newly_left_users,
)
@@ -1036,7 +1036,8 @@ class SyncHandler(object):
@measure_func("_generate_sync_entry_for_device_list")
@defer.inlineCallbacks
def _generate_sync_entry_for_device_list(self, sync_result_builder,
newly_joined_rooms, newly_joined_users,
newly_joined_rooms,
newly_joined_or_invited_users,
newly_left_rooms, newly_left_users):
user_id = sync_result_builder.sync_config.user.to_string()
since_token = sync_result_builder.since_token
@@ -1050,7 +1051,7 @@ class SyncHandler(object):
# share a room with?
for room_id in newly_joined_rooms:
joined_users = yield self.state.get_current_user_in_room(room_id)
newly_joined_users.update(joined_users)
newly_joined_or_invited_users.update(joined_users)
for room_id in newly_left_rooms:
left_users = yield self.state.get_current_user_in_room(room_id)
@@ -1058,7 +1059,7 @@ class SyncHandler(object):
# TODO: Check that these users are actually new, i.e. either they
# weren't in the previous sync *or* they left and rejoined.
changed.update(newly_joined_users)
changed.update(newly_joined_or_invited_users)
if not changed and not newly_left_users:
defer.returnValue(DeviceLists(
@@ -1176,7 +1177,7 @@ class SyncHandler(object):
@defer.inlineCallbacks
def _generate_sync_entry_for_presence(self, sync_result_builder, newly_joined_rooms,
newly_joined_users):
newly_joined_or_invited_users):
"""Generates the presence portion of the sync response. Populates the
`sync_result_builder` with the result.
@@ -1184,8 +1185,9 @@ class SyncHandler(object):
sync_result_builder(SyncResultBuilder)
newly_joined_rooms(list): List of rooms that the user has joined
since the last sync (or empty if an initial sync)
newly_joined_users(list): List of users that have joined rooms
since the last sync (or empty if an initial sync)
newly_joined_or_invited_users(list): List of users that have joined
or been invited to rooms since the last sync (or empty if an initial
sync)
"""
now_token = sync_result_builder.now_token
sync_config = sync_result_builder.sync_config
@@ -1211,7 +1213,7 @@ class SyncHandler(object):
"presence_key", presence_key
)
extra_users_ids = set(newly_joined_users)
extra_users_ids = set(newly_joined_or_invited_users)
for room_id in newly_joined_rooms:
users = yield self.state.get_current_user_in_room(room_id)
extra_users_ids.update(users)
@@ -1243,7 +1245,8 @@ class SyncHandler(object):
Returns:
Deferred(tuple): Returns a 4-tuple of
`(newly_joined_rooms, newly_joined_users, newly_left_rooms, newly_left_users)`
`(newly_joined_rooms, newly_joined_or_invited_users,
newly_left_rooms, newly_left_users)`
"""
user_id = sync_result_builder.sync_config.user.to_string()
block_all_room_ephemeral = (
@@ -1314,8 +1317,8 @@ class SyncHandler(object):
sync_result_builder.invited.extend(invited)
# Now we want to get any newly joined users
newly_joined_users = set()
# Now we want to get any newly joined or invited users
newly_joined_or_invited_users = set()
newly_left_users = set()
if since_token:
for joined_sync in sync_result_builder.joined:
@@ -1324,19 +1327,22 @@ class SyncHandler(object):
)
for event in it:
if event.type == EventTypes.Member:
if event.membership == Membership.JOIN:
newly_joined_users.add(event.state_key)
if (
event.membership == Membership.JOIN or
event.membership == Membership.INVITE
):
newly_joined_or_invited_users.add(event.state_key)
else:
prev_content = event.unsigned.get("prev_content", {})
prev_membership = prev_content.get("membership", None)
if prev_membership == Membership.JOIN:
newly_left_users.add(event.state_key)
newly_left_users -= newly_joined_users
newly_left_users -= newly_joined_or_invited_users
defer.returnValue((
newly_joined_rooms,
newly_joined_users,
newly_joined_or_invited_users,
newly_left_rooms,
newly_left_users,
))
@@ -1381,7 +1387,7 @@ class SyncHandler(object):
where:
room_entries is a list [RoomSyncResultBuilder]
invited_rooms is a list [InvitedSyncResult]
newly_joined rooms is a list[str] of room ids
newly_joined_rooms is a list[str] of room ids
newly_left_rooms is a list[str] of room ids
"""
user_id = sync_result_builder.sync_config.user.to_string()
@@ -1422,7 +1428,7 @@ class SyncHandler(object):
if room_id in sync_result_builder.joined_room_ids and non_joins:
# Always include if the user (re)joined the room, especially
# important so that device list changes are calculated correctly.
# If there are non join member events, but we are still in the room,
# If there are non-join member events, but we are still in the room,
# then the user must have left and joined
newly_joined_rooms.append(room_id)

View File

@@ -14,6 +14,8 @@
# limitations under the License.
""" This module contains REST servlets to do with profile: /profile/<paths> """
import logging
from twisted.internet import defer
from synapse.http.servlet import parse_json_object_from_request
@@ -21,6 +23,8 @@ from synapse.types import UserID
from .base import ClientV1RestServlet, client_path_patterns
logger = logging.getLogger(__name__)
class ProfileDisplaynameRestServlet(ClientV1RestServlet):
PATTERNS = client_path_patterns("/profile/(?P<user_id>[^/]*)/displayname")
@@ -28,6 +32,7 @@ class ProfileDisplaynameRestServlet(ClientV1RestServlet):
def __init__(self, hs):
super(ProfileDisplaynameRestServlet, self).__init__(hs)
self.profile_handler = hs.get_profile_handler()
self.http_client = hs.get_simple_http_client()
@defer.inlineCallbacks
def on_GET(self, request, user_id):
@@ -59,11 +64,30 @@ class ProfileDisplaynameRestServlet(ClientV1RestServlet):
yield self.profile_handler.set_displayname(
user, requester, new_name, is_admin)
if self.hs.config.shadow_server:
shadow_user = UserID(
user.localpart, self.hs.config.shadow_server.get("hs")
)
self.shadow_displayname(shadow_user.to_string(), content)
defer.returnValue((200, {}))
def on_OPTIONS(self, request, user_id):
return (200, {})
@defer.inlineCallbacks
def shadow_displayname(self, user_id, body):
# TODO: retries
shadow_hs_url = self.hs.config.shadow_server.get("hs_url")
as_token = self.hs.config.shadow_server.get("as_token")
yield self.http_client.put_json(
"%s/_matrix/client/r0/profile/%s/displayname?access_token=%s&user_id=%s" % (
shadow_hs_url, user_id, as_token, user_id
),
body
)
class ProfileAvatarURLRestServlet(ClientV1RestServlet):
PATTERNS = client_path_patterns("/profile/(?P<user_id>[^/]*)/avatar_url")
@@ -71,6 +95,7 @@ class ProfileAvatarURLRestServlet(ClientV1RestServlet):
def __init__(self, hs):
super(ProfileAvatarURLRestServlet, self).__init__(hs)
self.profile_handler = hs.get_profile_handler()
self.http_client = hs.get_simple_http_client()
@defer.inlineCallbacks
def on_GET(self, request, user_id):
@@ -101,11 +126,30 @@ class ProfileAvatarURLRestServlet(ClientV1RestServlet):
yield self.profile_handler.set_avatar_url(
user, requester, new_name, is_admin)
if self.hs.config.shadow_server:
shadow_user = UserID(
user.localpart, self.hs.config.shadow_server.get("hs")
)
self.shadow_avatar_url(shadow_user.to_string(), content)
defer.returnValue((200, {}))
def on_OPTIONS(self, request, user_id):
return (200, {})
@defer.inlineCallbacks
def shadow_avatar_url(self, user_id, body):
# TODO: retries
shadow_hs_url = self.hs.config.shadow_server.get("hs_url")
as_token = self.hs.config.shadow_server.get("as_token")
yield self.http_client.put_json(
"%s/_matrix/client/r0/profile/%s/avatar_url?access_token=%s&user_id=%s" % (
shadow_hs_url, user_id, as_token, user_id
),
body
)
class ProfileRestServlet(ClientV1RestServlet):
PATTERNS = client_path_patterns("/profile/(?P<user_id>[^/]*)")

View File

@@ -666,7 +666,8 @@ class RoomMembershipRestServlet(ClientV1RestServlet):
content["address"],
content["id_server"],
requester,
txn_id
txn_id,
new_room=False,
)
defer.returnValue((200, {}))
return

View File

@@ -27,6 +27,7 @@ from synapse.http.servlet import (
assert_params_in_dict,
parse_json_object_from_request,
)
from synapse.types import UserID
from synapse.util.msisdn import phone_number_to_msisdn
from synapse.util.threepids import check_3pid_allowed
@@ -51,10 +52,10 @@ class EmailPasswordRequestTokenRestServlet(RestServlet):
'id_server', 'client_secret', 'email', 'send_attempt'
])
if not check_3pid_allowed(self.hs, "email", body['email']):
if not (yield check_3pid_allowed(self.hs, "email", body['email'])):
raise SynapseError(
403,
"Your email domain is not authorized on this server",
"Your email is not authorized on this server",
Codes.THREEPID_DENIED,
)
@@ -89,7 +90,7 @@ class MsisdnPasswordRequestTokenRestServlet(RestServlet):
msisdn = phone_number_to_msisdn(body['country'], body['phone_number'])
if not check_3pid_allowed(self.hs, "msisdn", msisdn):
if not (yield check_3pid_allowed(self.hs, "msisdn", msisdn)):
raise SynapseError(
403,
"Account phone numbers are not authorized on this server",
@@ -117,6 +118,7 @@ class PasswordRestServlet(RestServlet):
self.auth_handler = hs.get_auth_handler()
self.datastore = self.hs.get_datastore()
self._set_password_handler = hs.get_set_password_handler()
self.http_client = hs.get_simple_http_client()
@interactive_auth_handler
@defer.inlineCallbacks
@@ -135,9 +137,13 @@ class PasswordRestServlet(RestServlet):
if self.auth.has_access_token(request):
requester = yield self.auth.get_user_by_req(request)
params = yield self.auth_handler.validate_user_via_ui_auth(
requester, body, self.hs.get_ip_from_request(request),
)
# blindly trust ASes without UI-authing them
if requester.app_service:
params = body
else:
params = yield self.auth_handler.validate_user_via_ui_auth(
requester, body, self.hs.get_ip_from_request(request),
)
user_id = requester.user.to_string()
else:
requester = None
@@ -173,11 +179,30 @@ class PasswordRestServlet(RestServlet):
user_id, new_password, requester
)
if self.hs.config.shadow_server:
shadow_user = UserID(
requester.user.localpart, self.hs.config.shadow_server.get("hs")
)
self.shadow_password(params, shadow_user.to_string())
defer.returnValue((200, {}))
def on_OPTIONS(self, _):
return 200, {}
@defer.inlineCallbacks
def shadow_password(self, body, user_id):
# TODO: retries
shadow_hs_url = self.hs.config.shadow_server.get("hs_url")
as_token = self.hs.config.shadow_server.get("as_token")
yield self.http_client.post_json_get_json(
"%s/_matrix/client/r0/account/password?access_token=%s&user_id=%s" % (
shadow_hs_url, as_token, user_id,
),
body
)
class DeactivateAccountRestServlet(RestServlet):
PATTERNS = client_v2_patterns("/account/deactivate$")
@@ -243,10 +268,10 @@ class EmailThreepidRequestTokenRestServlet(RestServlet):
['id_server', 'client_secret', 'email', 'send_attempt'],
)
if not check_3pid_allowed(self.hs, "email", body['email']):
if not (yield check_3pid_allowed(self.hs, "email", body['email'])):
raise SynapseError(
403,
"Your email domain is not authorized on this server",
"Your email is not authorized on this server",
Codes.THREEPID_DENIED,
)
@@ -280,7 +305,7 @@ class MsisdnThreepidRequestTokenRestServlet(RestServlet):
msisdn = phone_number_to_msisdn(body['country'], body['phone_number'])
if not check_3pid_allowed(self.hs, "msisdn", msisdn):
if not (yield check_3pid_allowed(self.hs, "msisdn", msisdn)):
raise SynapseError(
403,
"Account phone numbers are not authorized on this server",
@@ -307,7 +332,8 @@ class ThreepidRestServlet(RestServlet):
self.identity_handler = hs.get_handlers().identity_handler
self.auth = hs.get_auth()
self.auth_handler = hs.get_auth_handler()
self.datastore = self.hs.get_datastore()
self.datastore = hs.get_datastore()
self.http_client = hs.get_simple_http_client()
@defer.inlineCallbacks
def on_GET(self, request):
@@ -321,27 +347,38 @@ class ThreepidRestServlet(RestServlet):
@defer.inlineCallbacks
def on_POST(self, request):
body = parse_json_object_from_request(request)
if self.hs.config.disable_3pid_changes:
raise SynapseError(400, "3PID changes disabled on this server")
threePidCreds = body.get('threePidCreds')
threePidCreds = body.get('three_pid_creds', threePidCreds)
if threePidCreds is None:
raise SynapseError(400, "Missing param", Codes.MISSING_PARAM)
body = parse_json_object_from_request(request)
requester = yield self.auth.get_user_by_req(request)
user_id = requester.user.to_string()
threepid = yield self.identity_handler.threepid_from_creds(threePidCreds)
# skip validation if this is a shadow 3PID from an AS
if not requester.app_service:
threePidCreds = body.get('threePidCreds')
threePidCreds = body.get('three_pid_creds', threePidCreds)
if threePidCreds is None:
raise SynapseError(400, "Missing param", Codes.MISSING_PARAM)
if not threepid:
raise SynapseError(
400, "Failed to auth 3pid", Codes.THREEPID_AUTH_FAILED
)
threepid = yield self.identity_handler.threepid_from_creds(threePidCreds)
for reqd in ['medium', 'address', 'validated_at']:
if reqd not in threepid:
logger.warn("Couldn't add 3pid: invalid response from ID server")
raise SynapseError(500, "Invalid response from ID Server")
if not threepid:
raise SynapseError(
400, "Failed to auth 3pid", Codes.THREEPID_AUTH_FAILED
)
for reqd in ['medium', 'address', 'validated_at']:
if reqd not in threepid:
logger.warn("Couldn't add 3pid: invalid response from ID server")
raise SynapseError(500, "Invalid response from ID Server")
else:
# XXX: ASes pass in a validated threepid directly to bypass the IS.
# This makes the API entirely change shape when we have an AS token;
# it really should be an entirely separate API - perhaps
# /account/3pid/replicate or something.
threepid = body.get('threepid')
yield self.auth_handler.add_threepid(
user_id,
@@ -350,7 +387,7 @@ class ThreepidRestServlet(RestServlet):
threepid['validated_at'],
)
if 'bind' in body and body['bind']:
if not requester.app_service and ('bind' in body and body['bind']):
logger.debug(
"Binding threepid %s to %s",
threepid, user_id
@@ -359,19 +396,43 @@ class ThreepidRestServlet(RestServlet):
threePidCreds, user_id
)
if self.hs.config.shadow_server:
shadow_user = UserID(
requester.user.localpart, self.hs.config.shadow_server.get("hs")
)
self.shadow_3pid({'threepid': threepid}, shadow_user.to_string())
defer.returnValue((200, {}))
@defer.inlineCallbacks
def shadow_3pid(self, body, user_id):
# TODO: retries
shadow_hs_url = self.hs.config.shadow_server.get("hs_url")
as_token = self.hs.config.shadow_server.get("as_token")
yield self.http_client.post_json_get_json(
"%s/_matrix/client/r0/account/3pid?access_token=%s&user_id=%s" % (
shadow_hs_url, as_token, user_id,
),
body
)
class ThreepidDeleteRestServlet(RestServlet):
PATTERNS = client_v2_patterns("/account/3pid/delete$", releases=())
def __init__(self, hs):
super(ThreepidDeleteRestServlet, self).__init__()
self.hs = hs
self.auth = hs.get_auth()
self.auth_handler = hs.get_auth_handler()
self.http_client = hs.get_simple_http_client()
@defer.inlineCallbacks
def on_POST(self, request):
if self.hs.config.disable_3pid_changes:
raise SynapseError(400, "3PID changes disabled on this server")
body = parse_json_object_from_request(request)
assert_params_in_dict(body, ['medium', 'address'])
@@ -389,6 +450,12 @@ class ThreepidDeleteRestServlet(RestServlet):
logger.exception("Failed to remove threepid")
raise SynapseError(500, "Failed to remove threepid")
if self.hs.config.shadow_server:
shadow_user = UserID(
requester.user.localpart, self.hs.config.shadow_server.get("hs")
)
self.shadow_3pid_delete(body, shadow_user.to_string())
if ret:
id_server_unbind_result = "success"
else:
@@ -398,6 +465,19 @@ class ThreepidDeleteRestServlet(RestServlet):
"id_server_unbind_result": id_server_unbind_result,
}))
@defer.inlineCallbacks
def shadow_3pid_delete(self, body, user_id):
# TODO: retries
shadow_hs_url = self.hs.config.shadow_server.get("hs_url")
as_token = self.hs.config.shadow_server.get("as_token")
yield self.http_client.post_json_get_json(
"%s/_matrix/client/r0/account/3pid/delete?access_token=%s&user_id=%s" % (
shadow_hs_url, as_token, user_id
),
body
)
class WhoamiRestServlet(RestServlet):
PATTERNS = client_v2_patterns("/account/whoami$")

View File

@@ -19,6 +19,7 @@ from twisted.internet import defer
from synapse.api.errors import AuthError, NotFoundError, SynapseError
from synapse.http.servlet import RestServlet, parse_json_object_from_request
from synapse.types import UserID
from ._base import client_v2_patterns
@@ -39,6 +40,7 @@ class AccountDataServlet(RestServlet):
self.auth = hs.get_auth()
self.store = hs.get_datastore()
self.notifier = hs.get_notifier()
self._profile_handler = hs.get_profile_handler()
@defer.inlineCallbacks
def on_PUT(self, request, user_id, account_data_type):
@@ -48,6 +50,11 @@ class AccountDataServlet(RestServlet):
body = parse_json_object_from_request(request)
if account_data_type == "im.vector.hide_profile":
user = UserID.from_string(user_id)
hide_profile = body.get('hide_profile')
yield self._profile_handler.set_active(user, not hide_profile, True)
max_id = yield self.store.add_account_data_for_user(
user_id, account_data_type, body
)

View File

@@ -16,7 +16,9 @@
import hmac
import logging
import re
from hashlib import sha1
from string import capwords
from six import string_types
@@ -78,10 +80,10 @@ class EmailRegisterRequestTokenRestServlet(RestServlet):
'id_server', 'client_secret', 'email', 'send_attempt'
])
if not check_3pid_allowed(self.hs, "email", body['email']):
if not (yield check_3pid_allowed(self.hs, "email", body['email'])):
raise SynapseError(
403,
"Your email domain is not authorized to register on this server",
"Your email is not authorized to register on this server",
Codes.THREEPID_DENIED,
)
@@ -120,7 +122,7 @@ class MsisdnRegisterRequestTokenRestServlet(RestServlet):
msisdn = phone_number_to_msisdn(body['country'], body['phone_number'])
if not check_3pid_allowed(self.hs, "msisdn", msisdn):
if not (yield check_3pid_allowed(self.hs, "msisdn", msisdn)):
raise SynapseError(
403,
"Phone numbers are not authorized to register on this server",
@@ -249,6 +251,8 @@ class RegisterRestServlet(RestServlet):
raise SynapseError(400, "Invalid username")
desired_username = body['username']
desired_display_name = body.get('display_name')
appservice = None
if self.auth.has_access_token(request):
appservice = yield self.auth.get_appservice_by_req(request)
@@ -272,7 +276,8 @@ class RegisterRestServlet(RestServlet):
if isinstance(desired_username, string_types):
result = yield self._do_appservice_registration(
desired_username, access_token, body
desired_username, desired_password, desired_display_name,
access_token, body
)
defer.returnValue((200, result)) # we throw for non 200 responses
return
@@ -398,7 +403,7 @@ class RegisterRestServlet(RestServlet):
medium = auth_result[login_type]['medium']
address = auth_result[login_type]['address']
if not check_3pid_allowed(self.hs, medium, address):
if not (yield check_3pid_allowed(self.hs, medium, address)):
raise SynapseError(
403,
"Third party identifiers (email/phone numbers)" +
@@ -406,6 +411,84 @@ class RegisterRestServlet(RestServlet):
Codes.THREEPID_DENIED,
)
if self.hs.config.register_mxid_from_3pid:
# override the desired_username based on the 3PID if any.
# reset it first to avoid folks picking their own username.
desired_username = None
# we should have an auth_result at this point if we're going to progress
# to register the user (i.e. we haven't picked up a registered_user_id
# from our session store), in which case get ready and gen the
# desired_username
if auth_result:
if (
self.hs.config.register_mxid_from_3pid == 'email' and
LoginType.EMAIL_IDENTITY in auth_result
):
address = auth_result[LoginType.EMAIL_IDENTITY]['address']
desired_username = synapse.types.strip_invalid_mxid_characters(
address.replace('@', '-').lower()
)
# find a unique mxid for the account, suffixing numbers
# if needed
while True:
try:
yield self.registration_handler.check_username(
desired_username,
guest_access_token=guest_access_token,
assigned_user_id=registered_user_id,
)
# if we got this far we passed the check.
break
except SynapseError as e:
if e.errcode == Codes.USER_IN_USE:
m = re.match(r'^(.*?)(\d+)$', desired_username)
if m:
desired_username = m.group(1) + str(
int(m.group(2)) + 1
)
else:
desired_username += "1"
else:
# something else went wrong.
break
if self.hs.config.register_just_use_email_for_display_name:
desired_display_name = address
else:
# XXX: a nasty heuristic to turn an email address into
# a displayname, as part of register_mxid_from_3pid
parts = address.replace('.', ' ').split('@')
org_parts = parts[1].split(' ')
if org_parts[-2] == "matrix" and org_parts[-1] == "org":
org = "Tchap Admin"
elif org_parts[-2] == "gouv" and org_parts[-1] == "fr":
org = org_parts[-3] if len(org_parts) > 2 else org_parts[-2]
else:
org = org_parts[-2]
desired_display_name = (
capwords(parts[0]) + " [" + capwords(org) + "]"
)
elif (
self.hs.config.register_mxid_from_3pid == 'msisdn' and
LoginType.MSISDN in auth_result
):
desired_username = auth_result[LoginType.MSISDN]['address']
else:
raise SynapseError(
400, "Cannot derive mxid from 3pid; no recognised 3pid"
)
if desired_username is not None:
yield self.registration_handler.check_username(
desired_username,
guest_access_token=guest_access_token,
assigned_user_id=registered_user_id,
)
if registered_user_id is not None:
logger.info(
"Already registered user ID %r for this session",
@@ -417,9 +500,16 @@ class RegisterRestServlet(RestServlet):
# NB: This may be from the auth handler and NOT from the POST
assert_params_in_dict(params, ["password"])
desired_username = params.get("username", None)
if not self.hs.config.register_mxid_from_3pid:
desired_username = params.get("username", None)
else:
# we keep the original desired_username derived from the 3pid above
pass
guest_access_token = params.get("guest_access_token", None)
new_password = params.get("password", None)
# XXX: don't we need to validate these for length etc like we did on
# the ones from the JSON body earlier on in the method?
if desired_username is not None:
desired_username = desired_username.lower()
@@ -430,9 +520,10 @@ class RegisterRestServlet(RestServlet):
(registered_user_id, _) = yield self.registration_handler.register(
localpart=desired_username,
password=new_password,
password=params.get("password", None),
guest_access_token=guest_access_token,
generate_token=False,
default_display_name=desired_display_name,
threepid=threepid,
address=client_addr,
)
@@ -444,6 +535,14 @@ class RegisterRestServlet(RestServlet):
):
yield self.store.upsert_monthly_active_user(registered_user_id)
if self.hs.config.shadow_server:
yield self.registration_handler.shadow_register(
localpart=desired_username,
display_name=desired_display_name,
auth_result=auth_result,
params=params,
)
# remember that we've now registered that user account, and with
# what user ID (since the user may not have specified)
self.auth_handler.set_session_data(
@@ -471,11 +570,33 @@ class RegisterRestServlet(RestServlet):
return 200, {}
@defer.inlineCallbacks
def _do_appservice_registration(self, username, as_token, body):
def _do_appservice_registration(
self, username, password, display_name, as_token, body
):
# FIXME: appservice_register() is horribly duplicated with register()
# and they should probably just be combined together with a config flag.
user_id = yield self.registration_handler.appservice_register(
username, as_token
username, as_token, password, display_name
)
defer.returnValue((yield self._create_registration_details(user_id, body)))
result = yield self._create_registration_details(user_id, body)
auth_result = body.get('auth_result')
if auth_result and LoginType.EMAIL_IDENTITY in auth_result:
threepid = auth_result[LoginType.EMAIL_IDENTITY]
yield self._register_email_threepid(
user_id, threepid, result["access_token"],
body.get("bind_email")
)
if auth_result and LoginType.MSISDN in auth_result:
threepid = auth_result[LoginType.MSISDN]
yield self._register_msisdn_threepid(
user_id, threepid, result["access_token"],
body.get("bind_msisdn")
)
defer.returnValue(result)
@defer.inlineCallbacks
def _do_shared_secret_registration(self, username, password, body):

View File

@@ -15,6 +15,8 @@
import logging
from signedjson.sign import sign_json
from twisted.internet import defer
from synapse.api.errors import SynapseError
@@ -37,6 +39,7 @@ class UserDirectorySearchRestServlet(RestServlet):
self.hs = hs
self.auth = hs.get_auth()
self.user_directory_handler = hs.get_user_directory_handler()
self.http_client = hs.get_simple_http_client()
@defer.inlineCallbacks
def on_POST(self, request):
@@ -67,6 +70,14 @@ class UserDirectorySearchRestServlet(RestServlet):
body = parse_json_object_from_request(request)
if self.hs.config.user_directory_defer_to_id_server:
signed_body = sign_json(body, self.hs.hostname, self.hs.config.signing_key[0])
url = "%s/_matrix/identity/api/v1/user_directory/search" % (
self.hs.config.user_directory_defer_to_id_server,
)
resp = yield self.http_client.post_json_get_json(url, signed_body)
defer.returnValue((200, resp))
limit = body.get("limit", 10)
limit = min(limit, 50)

View File

View File

@@ -0,0 +1,159 @@
# -*- coding: utf-8 -*-
# Copyright 2018 New Vector Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from synapse.config._base import ConfigError
logger = logging.getLogger(__name__)
class DomainRuleChecker(object):
"""
A re-implementation of the SpamChecker that prevents users in one domain from
inviting users in other domains to rooms, based on a configuration.
Takes a config in the format:
spam_checker:
module: "rulecheck.DomainRuleChecker"
config:
domain_mapping:
"inviter_domain": [ "invitee_domain_permitted", "other_domain_permitted" ]
"other_inviter_domain": [ "invitee_domain_permitted" ]
default: False
# Only let local users join rooms if they were explicitly invited.
can_only_join_rooms_with_invite: false
# Only let local users create rooms if they are inviting only one
# other user, and that user matches the rules above.
can_only_create_one_to_one_rooms: false
# Only let local users invite during room creation, regardless of the
# domain mapping rules above.
can_only_invite_during_room_creation: false
# Allow third party invites
can_invite_by_third_party_id: true
Don't forget to consider if you can invite users from your own domain.
"""
def __init__(self, config):
self.domain_mapping = config["domain_mapping"] or {}
self.default = config["default"]
self.can_only_join_rooms_with_invite = config.get(
"can_only_join_rooms_with_invite", False,
)
self.can_only_create_one_to_one_rooms = config.get(
"can_only_create_one_to_one_rooms", False,
)
self.can_only_invite_during_room_creation = config.get(
"can_only_invite_during_room_creation", False,
)
self.can_invite_by_third_party_id = config.get(
"can_invite_by_third_party_id", True,
)
def check_event_for_spam(self, event):
"""Implements synapse.events.SpamChecker.check_event_for_spam
"""
return False
def user_may_invite(self, inviter_userid, invitee_userid, third_party_invite,
room_id, new_room):
"""Implements synapse.events.SpamChecker.user_may_invite
"""
if self.can_only_invite_during_room_creation and not new_room:
return False
if not self.can_invite_by_third_party_id and third_party_invite:
return False
# This is a third party invite (without a bound mxid), so unless we have
# banned all third party invites (above) we allow it.
if not invitee_userid:
return True
inviter_domain = self._get_domain_from_id(inviter_userid)
invitee_domain = self._get_domain_from_id(invitee_userid)
if inviter_domain not in self.domain_mapping:
return self.default
return invitee_domain in self.domain_mapping[inviter_domain]
def user_may_create_room(self, userid, invite_list, third_party_invite_list,
cloning):
"""Implements synapse.events.SpamChecker.user_may_create_room
"""
if cloning:
return True
if not self.can_invite_by_third_party_id and third_party_invite_list:
return False
number_of_invites = len(invite_list) + len(third_party_invite_list)
if self.can_only_create_one_to_one_rooms and number_of_invites != 1:
return False
return True
def user_may_create_room_alias(self, userid, room_alias):
"""Implements synapse.events.SpamChecker.user_may_create_room_alias
"""
return True
def user_may_publish_room(self, userid, room_id):
"""Implements synapse.events.SpamChecker.user_may_publish_room
"""
return True
def user_may_join_room(self, userid, room_id, is_invited):
"""Implements synapse.events.SpamChecker.user_may_join_room
"""
if self.can_only_join_rooms_with_invite and not is_invited:
return False
return True
@staticmethod
def parse_config(config):
"""Implements synapse.events.SpamChecker.parse_config
"""
if "default" in config:
return config
else:
raise ConfigError("No default set for spam_config DomainRuleChecker")
@staticmethod
def _get_domain_from_id(mxid):
"""Parses a string and returns the domain part of the mxid.
Args:
mxid (str): a valid mxid
Returns:
str: the domain part of the mxid
"""
idx = mxid.find(":")
if idx == -1:
raise Exception("Invalid ID: %r" % (mxid,))
return mxid[idx + 1:]

View File

@@ -35,7 +35,7 @@ def _make_exclusive_regex(services_cache):
exclusive_user_regexes = [
regex.pattern
for service in services_cache
for regex in service.get_exlusive_user_regexes()
for regex in service.get_exclusive_user_regexes()
]
if exclusive_user_regexes:
exclusive_user_regex = "|".join("(" + r + ")" for r in exclusive_user_regexes)

View File

@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2014-2016 OpenMarket Ltd
# Copyright 2018 New Vector Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -20,6 +21,8 @@ from synapse.storage.roommember import ProfileInfo
from ._base import SQLBaseStore
BATCH_SIZE = 100
class ProfileWorkerStore(SQLBaseStore):
@defer.inlineCallbacks
@@ -62,6 +65,55 @@ class ProfileWorkerStore(SQLBaseStore):
desc="get_profile_avatar_url",
)
def get_latest_profile_replication_batch_number(self):
def f(txn):
txn.execute("SELECT MAX(batch) as maxbatch FROM profiles")
rows = self.cursor_to_dict(txn)
return rows[0]['maxbatch']
return self.runInteraction(
"get_latest_profile_replication_batch_number", f,
)
def get_profile_batch(self, batchnum):
return self._simple_select_list(
table="profiles",
keyvalues={
"batch": batchnum,
},
retcols=("user_id", "displayname", "avatar_url", "active"),
desc="get_profile_batch",
)
def assign_profile_batch(self):
def f(txn):
sql = (
"UPDATE profiles SET batch = "
"(SELECT COALESCE(MAX(batch), -1) + 1 FROM profiles) "
"WHERE user_id in ("
" SELECT user_id FROM profiles WHERE batch is NULL limit ?"
")"
)
txn.execute(sql, (BATCH_SIZE,))
return txn.rowcount
return self.runInteraction("assign_profile_batch", f)
def get_replication_hosts(self):
def f(txn):
txn.execute("SELECT host, last_synced_batch FROM profile_replication_status")
rows = self.cursor_to_dict(txn)
return {r['host']: r['last_synced_batch'] for r in rows}
return self.runInteraction("get_replication_hosts", f)
def update_replication_batch_for_host(self, host, last_synced_batch):
return self._simple_upsert(
table="profile_replication_status",
keyvalues={"host": host},
values={
"last_synced_batch": last_synced_batch,
},
desc="update_replication_batch_for_host",
)
def get_from_remote_profile_cache(self, user_id):
return self._simple_select_one(
table="remote_profile_cache",
@@ -71,27 +123,46 @@ class ProfileWorkerStore(SQLBaseStore):
desc="get_from_remote_profile_cache",
)
def create_profile(self, user_localpart):
return self._simple_insert(
table="profiles",
values={"user_id": user_localpart},
desc="create_profile",
)
def set_profile_displayname(self, user_localpart, new_displayname):
return self._simple_update_one(
def set_profile_displayname(self, user_localpart, new_displayname, batchnum):
return self._simple_upsert(
table="profiles",
keyvalues={"user_id": user_localpart},
updatevalues={"displayname": new_displayname},
values={
"displayname": new_displayname,
"batch": batchnum,
},
desc="set_profile_displayname",
lock=False # we can do this because user_id has a unique index
)
def set_profile_avatar_url(self, user_localpart, new_avatar_url):
return self._simple_update_one(
def set_profile_avatar_url(self, user_localpart, new_avatar_url, batchnum):
return self._simple_upsert(
table="profiles",
keyvalues={"user_id": user_localpart},
updatevalues={"avatar_url": new_avatar_url},
values={
"avatar_url": new_avatar_url,
"batch": batchnum,
},
desc="set_profile_avatar_url",
lock=False # we can do this because user_id has a unique index
)
def set_profile_active(self, user_localpart, active, hide, batchnum):
values = {
"active": int(active),
"batch": batchnum,
}
if not active and not hide:
# we are deactivating for real (not in hide mode)
# so clear the profile.
values["avatar_url"] = None
values["displayname"] = None
return self._simple_upsert(
table="profiles",
keyvalues={"user_id": user_localpart},
values=values,
desc="set_profile_active",
lock=False # we can do this because user_id has a unique index
)

View File

@@ -0,0 +1,36 @@
/* Copyright 2018 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Add a batch number to track changes to profiles and the
* order they're made in so we can replicate user profiles
* to other hosts as they change
*/
ALTER TABLE profiles ADD COLUMN batch BIGINT DEFAULT NULL;
/*
* Index on the batch number so we can get profiles
* by their batch
*/
CREATE INDEX profiles_batch_idx ON profiles(batch);
/*
* A table to track what batch of user profiles has been
* synced to what profile replication target.
*/
CREATE TABLE profile_replication_status (
host TEXT NOT NULL,
last_synced_batch BIGINT NOT NULL
);

View File

@@ -0,0 +1,23 @@
/* Copyright 2018 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* A flag saying whether the user owning the profile has been deactivated
* This really belongs on the users table, not here, but the users table
* stores users by their full user_id and profiles stores them by localpart,
* so we can't easily join between the two tables. Plus, the batch number
* realy ought to represent data in this table that has changed.
*/
ALTER TABLE profiles ADD COLUMN active SMALLINT DEFAULT 1 NOT NULL;

View File

@@ -231,6 +231,18 @@ def contains_invalid_mxid_characters(localpart):
return any(c not in mxid_localpart_allowed_characters for c in localpart)
def strip_invalid_mxid_characters(localpart):
"""Removes any invalid characters from an mxid
Args:
localpart (basestring): the localpart to be stripped
Returns:
localpart (basestring): the localpart having been stripped
"""
return filter(lambda c: c in mxid_localpart_allowed_characters, localpart)
UPPER_CASE_PATTERN = re.compile(b"[A-Z_]")
# the following is a pattern which matches '=', and bytes which are not allowed in a mxid

View File

@@ -16,11 +16,14 @@
import logging
import re
from twisted.internet import defer
logger = logging.getLogger(__name__)
@defer.inlineCallbacks
def check_3pid_allowed(hs, medium, address):
"""Checks whether a given format of 3PID is allowed to be used on this HS
"""Checks whether a given 3PID is allowed to be used on this HS
Args:
hs (synapse.server.HomeServer): server
@@ -28,9 +31,35 @@ def check_3pid_allowed(hs, medium, address):
address (str): address within that medium (e.g. "wotan@matrix.org")
msisdns need to first have been canonicalised
Returns:
bool: whether the 3PID medium/address is allowed to be added to this HS
defered bool: whether the 3PID medium/address is allowed to be added to this HS
"""
if hs.config.check_is_for_allowed_local_3pids:
data = yield hs.get_simple_http_client().get_json(
"https://%s%s" % (
hs.config.check_is_for_allowed_local_3pids,
"/_matrix/identity/api/v1/internal-info"
),
{'medium': medium, 'address': address}
)
# Check for invalid response
if 'hs' not in data and 'shadow_hs' not in data:
defer.returnValue(False)
# Check if this user is intended to register for this homeserver
if (
data.get('hs') != hs.config.server_name
and data.get('shadow_hs') != hs.config.server_name
):
defer.returnValue(False)
if data.get('requires_invite', False) and not data.get('invited', False):
# Requires an invite but hasn't been invited
defer.returnValue(False)
defer.returnValue(True)
if hs.config.allowed_local_3pids:
for constraint in hs.config.allowed_local_3pids:
logger.debug(
@@ -41,8 +70,8 @@ def check_3pid_allowed(hs, medium, address):
medium == constraint['medium'] and
re.match(constraint['pattern'], address)
):
return True
defer.returnValue(True)
else:
return True
defer.returnValue(True)
return False
defer.returnValue(False)

View File

@@ -67,13 +67,13 @@ class ProfileTestCase(unittest.TestCase):
self.bob = UserID.from_string("@4567:test")
self.alice = UserID.from_string("@alice:remote")
yield self.store.create_profile(self.frank.localpart)
self.handler = hs.get_profile_handler()
@defer.inlineCallbacks
def test_get_my_name(self):
yield self.store.set_profile_displayname(self.frank.localpart, "Frank")
yield self.store.set_profile_displayname(
self.frank.localpart, "Frank", 1,
)
displayname = yield self.handler.get_displayname(self.frank)
@@ -116,8 +116,7 @@ class ProfileTestCase(unittest.TestCase):
@defer.inlineCallbacks
def test_incoming_fed_query(self):
yield self.store.create_profile("caroline")
yield self.store.set_profile_displayname("caroline", "Caroline")
yield self.store.set_profile_displayname("caroline", "Caroline", 1)
response = yield self.query_handlers["profile"](
{"user_id": "@caroline:test", "field": "displayname"}
@@ -128,7 +127,7 @@ class ProfileTestCase(unittest.TestCase):
@defer.inlineCallbacks
def test_get_my_avatar(self):
yield self.store.set_profile_avatar_url(
self.frank.localpart, "http://my.server/me.png"
self.frank.localpart, "http://my.server/me.png", 1,
)
avatar_url = yield self.handler.get_avatar_url(self.frank)

View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# Copyright 2018 New Vector Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

View File

@@ -0,0 +1,316 @@
# -*- coding: utf-8 -*-
# Copyright 2018 New Vector Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from synapse.config._base import ConfigError
from synapse.rest.client.v1 import admin, login, room
from synapse.rulecheck.domain_rule_checker import DomainRuleChecker
from tests import unittest
from tests.server import make_request, render
class DomainRuleCheckerTestCase(unittest.TestCase):
def test_allowed(self):
config = {
"default": False,
"domain_mapping": {
"source_one": ["target_one", "target_two"],
"source_two": ["target_two"],
},
}
check = DomainRuleChecker(config)
self.assertTrue(
check.user_may_invite(
"test:source_one", "test:target_one", None, "room", False
)
)
self.assertTrue(
check.user_may_invite(
"test:source_one", "test:target_two", None, "room", False
)
)
self.assertTrue(
check.user_may_invite(
"test:source_two", "test:target_two", None, "room", False
)
)
def test_disallowed(self):
config = {
"default": True,
"domain_mapping": {
"source_one": ["target_one", "target_two"],
"source_two": ["target_two"],
"source_four": [],
},
}
check = DomainRuleChecker(config)
self.assertFalse(
check.user_may_invite(
"test:source_one", "test:target_three", None, "room", False
)
)
self.assertFalse(
check.user_may_invite(
"test:source_two", "test:target_three", None, "room", False
)
)
self.assertFalse(
check.user_may_invite(
"test:source_two", "test:target_one", None, "room", False
)
)
self.assertFalse(
check.user_may_invite(
"test:source_four", "test:target_one", None, "room", False
)
)
def test_default_allow(self):
config = {
"default": True,
"domain_mapping": {
"source_one": ["target_one", "target_two"],
"source_two": ["target_two"],
},
}
check = DomainRuleChecker(config)
self.assertTrue(
check.user_may_invite(
"test:source_three", "test:target_one", None, "room", False
)
)
def test_default_deny(self):
config = {
"default": False,
"domain_mapping": {
"source_one": ["target_one", "target_two"],
"source_two": ["target_two"],
},
}
check = DomainRuleChecker(config)
self.assertFalse(
check.user_may_invite(
"test:source_three", "test:target_one", None, "room", False
)
)
def test_config_parse(self):
config = {
"default": False,
"domain_mapping": {
"source_one": ["target_one", "target_two"],
"source_two": ["target_two"],
},
}
self.assertEquals(config, DomainRuleChecker.parse_config(config))
def test_config_parse_failure(self):
config = {
"domain_mapping": {
"source_one": ["target_one", "target_two"],
"source_two": ["target_two"],
}
}
self.assertRaises(ConfigError, DomainRuleChecker.parse_config, config)
class DomainRuleCheckerRoomTestCase(unittest.HomeserverTestCase):
servlets = [
admin.register_servlets,
room.register_servlets,
login.register_servlets,
]
hijack_auth = False
def make_homeserver(self, reactor, clock):
config = self.default_config()
config.spam_checker = (
DomainRuleChecker,
{
"default": True,
"domain_mapping": {},
"can_only_join_rooms_with_invite": True,
"can_only_create_one_to_one_rooms": True,
"can_only_invite_during_room_creation": True,
"can_invite_by_third_party_id": False,
},
)
hs = self.setup_test_homeserver(config=config)
return hs
def prepare(self, reactor, clock, hs):
self.admin_user_id = self.register_user("admin_user", "pass", admin=True)
self.admin_access_token = self.login("admin_user", "pass")
self.normal_user_id = self.register_user("normal_user", "pass", admin=False)
self.normal_access_token = self.login("normal_user", "pass")
self.other_user_id = self.register_user("other_user", "pass", admin=False)
def test_admin_can_create_room(self):
channel = self._create_room(self.admin_access_token)
assert channel.result["code"] == b"200", channel.result
def test_normal_user_cannot_create_empty_room(self):
channel = self._create_room(self.normal_access_token)
assert channel.result["code"] == b"403", channel.result
def test_normal_user_cannot_create_room_with_multiple_invites(self):
channel = self._create_room(
self.normal_access_token,
content={"invite": [self.other_user_id, self.admin_user_id]},
)
assert channel.result["code"] == b"403", channel.result
# Test that it correctly counts both normal and third party invites
channel = self._create_room(
self.normal_access_token,
content={
"invite": [self.other_user_id],
"invite_3pid": [{"medium": "email", "address": "foo@example.com"}],
},
)
assert channel.result["code"] == b"403", channel.result
# Test that it correctly rejects third party invites
channel = self._create_room(
self.normal_access_token,
content={
"invite": [],
"invite_3pid": [{"medium": "email", "address": "foo@example.com"}],
},
)
assert channel.result["code"] == b"403", channel.result
def test_normal_user_can_room_with_single_invites(self):
channel = self._create_room(
self.normal_access_token, content={"invite": [self.other_user_id]}
)
assert channel.result["code"] == b"200", channel.result
def test_cannot_join_public_room(self):
channel = self._create_room(self.admin_access_token)
assert channel.result["code"] == b"200", channel.result
room_id = channel.json_body["room_id"]
self.helper.join(
room_id, self.normal_user_id, tok=self.normal_access_token, expect_code=403
)
def test_can_join_invited_room(self):
channel = self._create_room(self.admin_access_token)
assert channel.result["code"] == b"200", channel.result
room_id = channel.json_body["room_id"]
self.helper.invite(
room_id,
src=self.admin_user_id,
targ=self.normal_user_id,
tok=self.admin_access_token,
)
self.helper.join(
room_id, self.normal_user_id, tok=self.normal_access_token, expect_code=200
)
def test_cannot_invite(self):
channel = self._create_room(self.admin_access_token)
assert channel.result["code"] == b"200", channel.result
room_id = channel.json_body["room_id"]
self.helper.invite(
room_id,
src=self.admin_user_id,
targ=self.normal_user_id,
tok=self.admin_access_token,
)
self.helper.join(
room_id, self.normal_user_id, tok=self.normal_access_token, expect_code=200
)
self.helper.invite(
room_id,
src=self.normal_user_id,
targ=self.other_user_id,
tok=self.normal_access_token,
expect_code=403,
)
def test_cannot_3pid_invite(self):
"""Test that unbound 3pid invites get rejected.
"""
channel = self._create_room(self.admin_access_token)
assert channel.result["code"] == b"200", channel.result
room_id = channel.json_body["room_id"]
self.helper.invite(
room_id,
src=self.admin_user_id,
targ=self.normal_user_id,
tok=self.admin_access_token,
)
self.helper.join(
room_id, self.normal_user_id,
tok=self.normal_access_token,
expect_code=200,
)
self.helper.invite(
room_id,
src=self.normal_user_id,
targ=self.other_user_id,
tok=self.normal_access_token,
expect_code=403,
)
request, channel = self.make_request(
"POST",
"rooms/%s/invite" % (room_id),
{
"address": "foo@bar.com",
"medium": "email",
"id_server": "localhost"
},
access_token=self.normal_access_token,
)
self.render(request)
self.assertEqual(channel.code, 403, channel.result["body"])
def _create_room(self, token, content={}):
path = "/_matrix/client/r0/createRoom?access_token=%s" % (token,)
request, channel = make_request(
self.hs.get_reactor(),
"POST",
path,
content=json.dumps(content).encode("utf8"),
)
render(request, self.resource, self.hs.get_reactor())
return channel

View File

@@ -34,20 +34,19 @@ class ProfileStoreTestCase(unittest.TestCase):
@defer.inlineCallbacks
def test_displayname(self):
yield self.store.create_profile(self.u_frank.localpart)
yield self.store.set_profile_displayname(self.u_frank.localpart, "Frank")
yield self.store.set_profile_displayname(
self.u_frank.localpart, "Frank", 1,
)
self.assertEquals(
"Frank", (yield self.store.get_profile_displayname(self.u_frank.localpart))
"Frank",
(yield self.store.get_profile_displayname(self.u_frank.localpart))
)
@defer.inlineCallbacks
def test_avatar_url(self):
yield self.store.create_profile(self.u_frank.localpart)
yield self.store.set_profile_avatar_url(
self.u_frank.localpart, "http://my.site/here"
self.u_frank.localpart, "http://my.site/here", 1,
)
self.assertEquals(

View File

@@ -144,6 +144,7 @@ def default_config(name):
config.federation_rc_concurrent = 10
config.filter_timeline_limit = 5000
config.user_directory_search_all_users = False
config.replicate_user_profiles_to = []
config.user_consent_server_notice_content = None
config.block_events_without_consent_error = None
config.user_consent_at_registration = False
@@ -162,6 +163,8 @@ def default_config(name):
config.admin_contact = None
config.rc_messages_per_second = 10000
config.rc_message_burst_count = 10000
config.register_mxid_from_3pid = None
config.shadow_server = None
config.rc_registration.per_second = 10000
config.rc_registration.burst_count = 10000
config.rc_login_address.per_second = 10000