Stop advertising unsupported flows for registration (#6107)

If email or msisdn verification aren't supported, let's stop advertising them
for registration.

Fixes #6100.
This commit is contained in:
Richard van der Hoff 2019-09-25 12:10:26 +01:00 committed by GitHub
parent 2cd98812ba
commit 990928abde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 83 additions and 16 deletions

1
changelog.d/6107.bugfix Normal file
View File

@ -0,0 +1 @@
Ensure that servers which are not configured to support email address verification do not offer it in the registration flows.

View File

@ -61,7 +61,8 @@ class AuthHandler(BaseHandler):
self.checkers = {} # type: dict[str, UserInteractiveAuthChecker] self.checkers = {} # type: dict[str, UserInteractiveAuthChecker]
for auth_checker_class in INTERACTIVE_AUTH_CHECKERS: for auth_checker_class in INTERACTIVE_AUTH_CHECKERS:
inst = auth_checker_class(hs) inst = auth_checker_class(hs)
self.checkers[inst.AUTH_TYPE] = inst if inst.is_enabled():
self.checkers[inst.AUTH_TYPE] = inst
self.bcrypt_rounds = hs.config.bcrypt_rounds self.bcrypt_rounds = hs.config.bcrypt_rounds
@ -156,6 +157,14 @@ class AuthHandler(BaseHandler):
return params return params
def get_enabled_auth_types(self):
"""Return the enabled user-interactive authentication types
Returns the UI-Auth types which are supported by the homeserver's current
config.
"""
return self.checkers.keys()
@defer.inlineCallbacks @defer.inlineCallbacks
def check_auth(self, flows, clientdict, clientip): def check_auth(self, flows, clientdict, clientip):
""" """

View File

@ -32,6 +32,13 @@ class UserInteractiveAuthChecker:
def __init__(self, hs): def __init__(self, hs):
pass pass
def is_enabled(self):
"""Check if the configuration of the homeserver allows this checker to work
Returns:
bool: True if this login type is enabled.
"""
def check_auth(self, authdict, clientip): def check_auth(self, authdict, clientip):
"""Given the authentication dict from the client, attempt to check this step """Given the authentication dict from the client, attempt to check this step
@ -51,6 +58,9 @@ class UserInteractiveAuthChecker:
class DummyAuthChecker(UserInteractiveAuthChecker): class DummyAuthChecker(UserInteractiveAuthChecker):
AUTH_TYPE = LoginType.DUMMY AUTH_TYPE = LoginType.DUMMY
def is_enabled(self):
return True
def check_auth(self, authdict, clientip): def check_auth(self, authdict, clientip):
return defer.succeed(True) return defer.succeed(True)
@ -58,6 +68,9 @@ class DummyAuthChecker(UserInteractiveAuthChecker):
class TermsAuthChecker(UserInteractiveAuthChecker): class TermsAuthChecker(UserInteractiveAuthChecker):
AUTH_TYPE = LoginType.TERMS AUTH_TYPE = LoginType.TERMS
def is_enabled(self):
return True
def check_auth(self, authdict, clientip): def check_auth(self, authdict, clientip):
return defer.succeed(True) return defer.succeed(True)
@ -67,10 +80,14 @@ class RecaptchaAuthChecker(UserInteractiveAuthChecker):
def __init__(self, hs): def __init__(self, hs):
super().__init__(hs) super().__init__(hs)
self._enabled = bool(hs.config.recaptcha_private_key)
self._http_client = hs.get_simple_http_client() self._http_client = hs.get_simple_http_client()
self._url = hs.config.recaptcha_siteverify_api self._url = hs.config.recaptcha_siteverify_api
self._secret = hs.config.recaptcha_private_key self._secret = hs.config.recaptcha_private_key
def is_enabled(self):
return self._enabled
@defer.inlineCallbacks @defer.inlineCallbacks
def check_auth(self, authdict, clientip): def check_auth(self, authdict, clientip):
try: try:
@ -191,6 +208,12 @@ class EmailIdentityAuthChecker(UserInteractiveAuthChecker, _BaseThreepidAuthChec
UserInteractiveAuthChecker.__init__(self, hs) UserInteractiveAuthChecker.__init__(self, hs)
_BaseThreepidAuthChecker.__init__(self, hs) _BaseThreepidAuthChecker.__init__(self, hs)
def is_enabled(self):
return self.hs.config.threepid_behaviour_email in (
ThreepidBehaviour.REMOTE,
ThreepidBehaviour.LOCAL,
)
def check_auth(self, authdict, clientip): def check_auth(self, authdict, clientip):
return self._check_threepid("email", authdict) return self._check_threepid("email", authdict)
@ -202,6 +225,9 @@ class MsisdnAuthChecker(UserInteractiveAuthChecker, _BaseThreepidAuthChecker):
UserInteractiveAuthChecker.__init__(self, hs) UserInteractiveAuthChecker.__init__(self, hs)
_BaseThreepidAuthChecker.__init__(self, hs) _BaseThreepidAuthChecker.__init__(self, hs)
def is_enabled(self):
return bool(self.hs.config.account_threepid_delegate_msisdn)
def check_auth(self, authdict, clientip): def check_auth(self, authdict, clientip):
return self._check_threepid("msisdn", authdict) return self._check_threepid("msisdn", authdict)

View File

@ -32,12 +32,14 @@ from synapse.api.errors import (
ThreepidValidationError, ThreepidValidationError,
UnrecognizedRequestError, UnrecognizedRequestError,
) )
from synapse.config import ConfigError
from synapse.config.captcha import CaptchaConfig from synapse.config.captcha import CaptchaConfig
from synapse.config.consent_config import ConsentConfig from synapse.config.consent_config import ConsentConfig
from synapse.config.emailconfig import ThreepidBehaviour from synapse.config.emailconfig import ThreepidBehaviour
from synapse.config.ratelimiting import FederationRateLimitConfig from synapse.config.ratelimiting import FederationRateLimitConfig
from synapse.config.registration import RegistrationConfig from synapse.config.registration import RegistrationConfig
from synapse.config.server import is_threepid_reserved from synapse.config.server import is_threepid_reserved
from synapse.handlers.auth import AuthHandler
from synapse.http.server import finish_request from synapse.http.server import finish_request
from synapse.http.servlet import ( from synapse.http.servlet import (
RestServlet, RestServlet,
@ -375,7 +377,9 @@ class RegisterRestServlet(RestServlet):
self.ratelimiter = hs.get_registration_ratelimiter() self.ratelimiter = hs.get_registration_ratelimiter()
self.clock = hs.get_clock() self.clock = hs.get_clock()
self._registration_flows = _calculate_registration_flows(hs.config) self._registration_flows = _calculate_registration_flows(
hs.config, self.auth_handler
)
@interactive_auth_handler @interactive_auth_handler
@defer.inlineCallbacks @defer.inlineCallbacks
@ -664,11 +668,13 @@ class RegisterRestServlet(RestServlet):
def _calculate_registration_flows( def _calculate_registration_flows(
# technically `config` has to provide *all* of these interfaces, not just one # technically `config` has to provide *all* of these interfaces, not just one
config: Union[RegistrationConfig, ConsentConfig, CaptchaConfig], config: Union[RegistrationConfig, ConsentConfig, CaptchaConfig],
auth_handler: AuthHandler,
) -> List[List[str]]: ) -> List[List[str]]:
"""Get a suitable flows list for registration """Get a suitable flows list for registration
Args: Args:
config: server configuration config: server configuration
auth_handler: authorization handler
Returns: a list of supported flows Returns: a list of supported flows
""" """
@ -678,10 +684,29 @@ def _calculate_registration_flows(
require_msisdn = "msisdn" in config.registrations_require_3pid require_msisdn = "msisdn" in config.registrations_require_3pid
show_msisdn = True show_msisdn = True
show_email = True
if config.disable_msisdn_registration: if config.disable_msisdn_registration:
show_msisdn = False show_msisdn = False
require_msisdn = False require_msisdn = False
enabled_auth_types = auth_handler.get_enabled_auth_types()
if LoginType.EMAIL_IDENTITY not in enabled_auth_types:
show_email = False
if require_email:
raise ConfigError(
"Configuration requires email address at registration, but email "
"validation is not configured"
)
if LoginType.MSISDN not in enabled_auth_types:
show_msisdn = False
if require_msisdn:
raise ConfigError(
"Configuration requires msisdn at registration, but msisdn "
"validation is not configured"
)
flows = [] flows = []
# only support 3PIDless registration if no 3PIDs are required # only support 3PIDless registration if no 3PIDs are required
@ -693,14 +718,15 @@ def _calculate_registration_flows(
flows.append([LoginType.DUMMY]) flows.append([LoginType.DUMMY])
# only support the email-only flow if we don't require MSISDN 3PIDs # only support the email-only flow if we don't require MSISDN 3PIDs
if not require_msisdn: if show_email and not require_msisdn:
flows.append([LoginType.EMAIL_IDENTITY]) flows.append([LoginType.EMAIL_IDENTITY])
# only support the MSISDN-only flow if we don't require email 3PIDs # only support the MSISDN-only flow if we don't require email 3PIDs
if show_msisdn and not require_email: if show_msisdn and not require_email:
flows.append([LoginType.MSISDN]) flows.append([LoginType.MSISDN])
if show_msisdn: if show_email and show_msisdn:
# always let users provide both MSISDN & email
flows.append([LoginType.MSISDN, LoginType.EMAIL_IDENTITY]) flows.append([LoginType.MSISDN, LoginType.EMAIL_IDENTITY])
# Prepend m.login.terms to all flows if we're requiring consent # Prepend m.login.terms to all flows if we're requiring consent

View File

@ -198,16 +198,8 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase):
self.assertEquals(channel.result["code"], b"401", channel.result) self.assertEquals(channel.result["code"], b"401", channel.result)
flows = channel.json_body["flows"] flows = channel.json_body["flows"]
# with the stock config, we expect all four combinations of 3pid # with the stock config, we only expect the dummy flow
self.assertCountEqual( self.assertCountEqual([["m.login.dummy"]], (f["stages"] for f in flows))
[
["m.login.dummy"],
["m.login.email.identity"],
["m.login.msisdn"],
["m.login.msisdn", "m.login.email.identity"],
],
(f["stages"] for f in flows),
)
@unittest.override_config( @unittest.override_config(
{ {
@ -217,9 +209,13 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase):
"template_dir": "/", "template_dir": "/",
"require_at_registration": True, "require_at_registration": True,
}, },
"account_threepid_delegates": {
"email": "https://id_server",
"msisdn": "https://id_server",
},
} }
) )
def test_advertised_flows_captcha_and_terms(self): def test_advertised_flows_captcha_and_terms_and_3pids(self):
request, channel = self.make_request(b"POST", self.url, b"{}") request, channel = self.make_request(b"POST", self.url, b"{}")
self.render(request) self.render(request)
self.assertEquals(channel.result["code"], b"401", channel.result) self.assertEquals(channel.result["code"], b"401", channel.result)
@ -241,7 +237,16 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase):
) )
@unittest.override_config( @unittest.override_config(
{"registrations_require_3pid": ["email"], "disable_msisdn_registration": True} {
"public_baseurl": "https://test_server",
"registrations_require_3pid": ["email"],
"disable_msisdn_registration": True,
"email": {
"smtp_host": "mail_server",
"smtp_port": 2525,
"notif_from": "sender@host",
},
}
) )
def test_advertised_flows_no_msisdn_email_required(self): def test_advertised_flows_no_msisdn_email_required(self):
request, channel = self.make_request(b"POST", self.url, b"{}") request, channel = self.make_request(b"POST", self.url, b"{}")