-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
add registrations_require_3pid and allow_local_3pids #2813
Changes from 6 commits
28a6ccb
81d037d
0af58f1
9d332e0
447f4f0
293380b
8fe253f
62d7d66
49fce04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,8 @@ def read_config(self, config): | |
strtobool(str(config["disable_registration"])) | ||
) | ||
|
||
self.registrations_require_3pid = config.get("registrations_require_3pid", []) | ||
self.allowed_local_3pids = config.get("allowed_local_3pids", []) | ||
self.registration_shared_secret = config.get("registration_shared_secret") | ||
|
||
self.bcrypt_rounds = config.get("bcrypt_rounds", 12) | ||
|
@@ -52,6 +54,23 @@ def default_config(self, **kwargs): | |
# Enable registration for new users. | ||
enable_registration: False | ||
|
||
# The user must provide all of the below types of 3PID when registering. | ||
# | ||
# registrations_require_3pid: | ||
# - msisdn | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Personally I would have the two config option entirely separate, as this doesn't support the use case of "require a work email but let people bind personal emails" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we're not trying to solve for that use case here, plus having talked to @dbkr it's hard to do anyway - as /account/3pid goes and adds the email to your account (letting you log in with it) as well as optionally binding it on the identity server... so you can't separate the two easily anyway. Hence me splitting it into:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. btw, this is a good example of a review comment where I'm staring at it thinking "does he really want me to rewrite it again? or is he just voicing a random comment?". It might be worth clearly spelling out which comments actually block accepting the PR from your POV, otherwise the committer has no option than assume everything is a request for change... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, in this case it was an observation that this feels odd and questioning whether this is the right thing to do. The synapse review process is not just about whether functional changes are right, but if we should even be doing it like that. |
||
|
||
# Mandate that users are only allowed to associate certain formats of | ||
# 3PIDs with accounts on this server. | ||
# | ||
# allowed_local_3pids: | ||
# - medium: email | ||
# pattern: ".*@matrix\\.org" | ||
# - medium: email | ||
# pattern: ".*@vector\\.im" | ||
# - medium: msisdn | ||
# pattern: "\\+44" | ||
|
||
# If set, allows registration by anyone who also has the shared | ||
# secret, even if registration is otherwise disabled. | ||
registration_shared_secret: "%(registration_shared_secret)s" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -70,10 +70,15 @@ def __init__(self, hs): | |
self.handlers = hs.get_handlers() | ||
|
||
def on_GET(self, request): | ||
|
||
require_email = 'email' in self.hs.config.registrations_require_3pid | ||
require_msisdn = 'msisdn' in self.hs.config.registrations_require_3pid | ||
|
||
flows = [] | ||
if self.hs.config.enable_registration_captcha: | ||
return ( | ||
200, | ||
{"flows": [ | ||
# only support the email-only flow if we don't require MSISDN 3PIDs | ||
if not require_msisdn: | ||
flows.extend([ | ||
{ | ||
"type": LoginType.RECAPTCHA, | ||
"stages": [ | ||
|
@@ -82,27 +87,34 @@ def on_GET(self, request): | |
LoginType.PASSWORD | ||
] | ||
}, | ||
]) | ||
# only support 3PIDless registration if no 3PIDs are required | ||
if not require_email and not require_msisdn: | ||
flows.extend([ | ||
{ | ||
"type": LoginType.RECAPTCHA, | ||
"stages": [LoginType.RECAPTCHA, LoginType.PASSWORD] | ||
} | ||
]} | ||
) | ||
]) | ||
else: | ||
return ( | ||
200, | ||
{"flows": [ | ||
# only support the email-only flow if we don't require MSISDN 3PIDs | ||
if require_email or not require_msisdn: | ||
flows.extend([ | ||
{ | ||
"type": LoginType.EMAIL_IDENTITY, | ||
"stages": [ | ||
LoginType.EMAIL_IDENTITY, LoginType.PASSWORD | ||
] | ||
}, | ||
} | ||
]) | ||
# only support 3PIDless registration if no 3PIDs are required | ||
if not require_email and not require_msisdn: | ||
flows.extend([ | ||
{ | ||
"type": LoginType.PASSWORD | ||
} | ||
]} | ||
) | ||
]) | ||
return (200, {"flows": flows}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does Also I really can't follow whats going on here, there are two many if and nots and ands and ors. It seems that we're building up flows based on required stages, so something like the following might work better: required_stages = [] # The stages that need to be in every flow
if self.hs.config.enable_registration_captcha:
required_stages.append(LoginType.RECAPTCHA)
if require_email:
required_stages.append(LoginType.EMAIL_IDENTITY)
# ... etc ...
# Now we work out what flows we need to offer.
flows = []
# First we add the base flow with only the required stages:
flows.append({"stages": required_stages})
# If we don't require email, then we add a flow to let people optionally specify it
if not require_email:
flows.append({"stages": required_stages + [LoginType.EMAIL_IDENTITY]}) |
||
|
||
@defer.inlineCallbacks | ||
def on_POST(self, request): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,7 @@ | |
RestServlet, parse_json_object_from_request, assert_params_in_request, parse_string | ||
) | ||
from synapse.util.msisdn import phone_number_to_msisdn | ||
from synapse.util.threepids import check_3pid_allowed | ||
|
||
from ._base import client_v2_patterns, interactive_auth_handler | ||
|
||
|
@@ -70,6 +71,11 @@ def on_POST(self, request): | |
'id_server', 'client_secret', 'email', 'send_attempt' | ||
]) | ||
|
||
if not check_3pid_allowed(self.hs, "email", body['email']): | ||
raise SynapseError( | ||
403, "Third party identifier is not allowed", Codes.THREEPID_DENIED, | ||
) | ||
|
||
existingUid = yield self.hs.get_datastore().get_user_id_by_threepid( | ||
'email', body['email'] | ||
) | ||
|
@@ -105,6 +111,11 @@ def on_POST(self, request): | |
|
||
msisdn = phone_number_to_msisdn(body['country'], body['phone_number']) | ||
|
||
if not check_3pid_allowed(self.hs, "msisdn", msisdn): | ||
raise SynapseError( | ||
403, "Third party identifier is not allowed", Codes.THREEPID_DENIED, | ||
) | ||
|
||
existingUid = yield self.hs.get_datastore().get_user_id_by_threepid( | ||
'msisdn', msisdn | ||
) | ||
|
@@ -305,31 +316,70 @@ def on_POST(self, request): | |
if 'x_show_msisdn' in body and body['x_show_msisdn']: | ||
show_msisdn = True | ||
|
||
# FIXME: need a better error than "no auth flow found" for scenarios | ||
# where we required 3PID for registration but the user didn't give one | ||
require_email = 'email' in self.hs.config.registrations_require_3pid | ||
require_msisdn = 'msisdn' in self.hs.config.registrations_require_3pid | ||
|
||
flows = [] | ||
if self.hs.config.enable_registration_captcha: | ||
flows = [ | ||
[LoginType.RECAPTCHA], | ||
[LoginType.EMAIL_IDENTITY, LoginType.RECAPTCHA], | ||
] | ||
# only support 3PIDless registration if no 3PIDs are required | ||
if not require_email and not require_msisdn: | ||
flows.extend([[LoginType.RECAPTCHA]]) | ||
# only support the email-only flow if we don't require MSISDN 3PIDs | ||
if not require_msisdn: | ||
flows.extend([[LoginType.EMAIL_IDENTITY, LoginType.RECAPTCHA]]) | ||
|
||
if show_msisdn: | ||
# only support the MSISDN-only flow if we don't require email 3PIDs | ||
if not require_email: | ||
flows.extend([[LoginType.MSISDN, LoginType.RECAPTCHA]]) | ||
# always let users provide both MSISDN & email | ||
flows.extend([ | ||
[LoginType.MSISDN, LoginType.RECAPTCHA], | ||
[LoginType.MSISDN, LoginType.EMAIL_IDENTITY, LoginType.RECAPTCHA], | ||
]) | ||
else: | ||
flows = [ | ||
[LoginType.DUMMY], | ||
[LoginType.EMAIL_IDENTITY], | ||
] | ||
# only support 3PIDless registration if no 3PIDs are required | ||
if not require_email and not require_msisdn: | ||
flows.extend([[LoginType.DUMMY]]) | ||
# only support the email-only flow if we don't require MSISDN 3PIDs | ||
if not require_msisdn: | ||
flows.extend([[LoginType.EMAIL_IDENTITY]]) | ||
|
||
if show_msisdn: | ||
# only support the MSISDN-only flow if we don't require email 3PIDs | ||
if not require_email or require_msisdn: | ||
flows.extend([[LoginType.MSISDN]]) | ||
# always let users provide both MSISDN & email | ||
flows.extend([ | ||
[LoginType.MSISDN], | ||
[LoginType.MSISDN, LoginType.EMAIL_IDENTITY], | ||
[LoginType.MSISDN, LoginType.EMAIL_IDENTITY] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (See comment where we do this in v1 register) |
||
]) | ||
|
||
auth_result, params, session_id = yield self.auth_handler.check_auth( | ||
flows, body, self.hs.get_ip_from_request(request) | ||
) | ||
|
||
# Check that we're not trying to register a denied 3pid. | ||
# | ||
# the user-facing checks will probably already have happened in | ||
# /register/email/requestToken when we requested a 3pid, but that's not | ||
# guaranteed. | ||
|
||
if ( | ||
auth_result and | ||
( | ||
LoginType.EMAIL_IDENTITY in auth_result or | ||
LoginType.EMAIL_MSISDN in auth_result | ||
) | ||
): | ||
medium = auth_result[LoginType.EMAIL_IDENTITY].threepid['medium'] | ||
address = auth_result[LoginType.EMAIL_IDENTITY].threepid['address'] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't this need to check both msisdn and email? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oops, thanks |
||
|
||
if not check_3pid_allowed(self.hs, medium, address): | ||
raise SynapseError( | ||
403, "Third party identifier is not allowed", Codes.THREEPID_DENIED, | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we not use |
||
|
||
if registered_user_id is not None: | ||
logger.info( | ||
"Already registered user ID %r for this session", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# -*- 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 | ||
import re | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def check_3pid_allowed(hs, medium, address): | ||
"""Checks whether a given format of 3PID is allowed to be used on this HS | ||
|
||
Args: | ||
hs (synapse.server.HomeServer): server | ||
medium (str): 3pid medium - e.g. email, msisdn | ||
address (str): address within that medium (e.g. "[email protected]") | ||
msisdns need to first have been canonicalised | ||
""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should have a:
especially since I think there are other |
||
|
||
if hs.config.allowed_local_3pids: | ||
for constraint in hs.config.allowed_local_3pids: | ||
logger.debug("Checking 3PID %s (%s) against %s (%s)" % ( | ||
address, medium, constraint['pattern'], constraint['medium'] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't need explicit string formatting, logger.debug does it for you. |
||
)) | ||
if ( | ||
medium == constraint['medium'] and | ||
re.match(constraint['pattern'], address) | ||
): | ||
return True | ||
else: | ||
return True | ||
|
||
return False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem to affect just registrations and could be misleading to some people.
Edit for clarity: I'm talking about the config key name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, i've asked @dbkr to clarify the impact on the other 3PID manipulations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this convo ended up in IRL as dave presumably missed this comment: the impact is: