diff --git a/synapse/handlers/auth.py b/synapse/handlers/auth.py index 119678e67b..98d8a86d14 100644 --- a/synapse/handlers/auth.py +++ b/synapse/handlers/auth.py @@ -45,12 +45,93 @@ from synapse.metrics.background_process_metrics import run_as_background_process from synapse.module_api import ModuleApi from synapse.push.mailer import load_jinja2_templates from synapse.types import Requester, UserID +from synapse.util.msisdn import phone_number_to_msisdn from ._base import BaseHandler logger = logging.getLogger(__name__) +def client_dict_convert_legacy_fields_to_identifier( + submission: Dict[str, Union[str, Dict]] +): + """Take a legacy-formatted login submission or User-Interactive Authentication dict and + updates it to feature an identifier dict instead. + Providing user-identifying information at the top-level of a login or UIA submission is + now deprecated and replaced with identifiers: + https://matrix.org/docs/spec/client_server/r0.6.1#identifier-types + Args: + submission: The client dict to convert. Passed by reference and modified + Raises: + SynapseError: if the dict contains a "medium" parameter that is anything other than + "email" + """ + if "user" in submission: + submission["identifier"] = {"type": "m.id.user", "user": submission["user"]} + del submission["user"] + + if "medium" in submission and "address" in submission: + submission["identifier"] = { + "type": "m.id.thirdparty", + "medium": submission["medium"], + "address": submission["address"], + } + del submission["medium"] + del submission["address"] + + # We've converted valid, legacy login submissions to an identifier. If the + # dict still doesn't have an identifier, it's invalid + if "identifier" not in submission: + raise SynapseError( + 400, + "Missing 'identifier' parameter in login submission", + errcode=Codes.MISSING_PARAM, + ) + + # Ensure the identifier has a type + if "type" not in submission["identifier"]: + raise SynapseError( + 400, "'identifier' dict has no key 'type'", errcode=Codes.MISSING_PARAM, + ) + + +def login_id_phone_to_thirdparty(identifier: Dict[str, str]): + """Convert a phone login identifier type to a generic threepid identifier. Modifies + the identifier dict in place + Args: + identifier: Login identifier dict of type 'm.id.phone' + """ + if "type" not in identifier: + raise SynapseError( + 400, "Invalid phone-type identifier", errcode=Codes.MISSING_PARAM + ) + + if "country" not in identifier or ( + # XXX: We used to require `number` instead of `phone`. The spec + # defines `phone`. So accept both + "phone" not in identifier + and "number" not in identifier + ): + raise SynapseError( + 400, "Invalid phone-type identifier", errcode=Codes.INVALID_PARAM + ) + + # Accept both "phone" and "number" as valid keys in m.id.phone + phone_number = identifier.get("phone", identifier.get("number")) + + # Convert user-provided phone number to a consistent representation + msisdn = phone_number_to_msisdn(identifier["country"], phone_number) + + # Modify the passed dictionary by reference + del identifier["country"] + identifier.pop("number", None) + identifier.pop("phone", None) + + identifier["type"] = "m.id.thirdparty" + identifier["medium"] = "msisdn" + identifier["address"] = msisdn + + class AuthHandler(BaseHandler): SESSION_EXPIRE_MS = 48 * 60 * 60 * 1000 diff --git a/synapse/rest/client/v1/login.py b/synapse/rest/client/v1/login.py index dceb2792fa..e106e109ee 100644 --- a/synapse/rest/client/v1/login.py +++ b/synapse/rest/client/v1/login.py @@ -17,6 +17,10 @@ import logging from synapse.api.errors import Codes, LoginError, SynapseError from synapse.api.ratelimiting import Ratelimiter +from synapse.handlers.auth import ( + client_dict_convert_legacy_fields_to_identifier, + login_id_phone_to_thirdparty, +) from synapse.http.server import finish_request from synapse.http.servlet import ( RestServlet, @@ -27,47 +31,10 @@ from synapse.http.site import SynapseRequest from synapse.rest.client.v2_alpha._base import client_patterns from synapse.rest.well_known import WellKnownBuilder from synapse.types import UserID -from synapse.util.msisdn import phone_number_to_msisdn logger = logging.getLogger(__name__) -def login_submission_legacy_convert(submission): - """ - If the input login submission is an old style object - (ie. with top-level user / medium / address) convert it - to a typed object. - """ - if "user" in submission: - submission["identifier"] = {"type": "m.id.user", "user": submission["user"]} - del submission["user"] - - if "medium" in submission and "address" in submission: - submission["identifier"] = { - "type": "m.id.thirdparty", - "medium": submission["medium"], - "address": submission["address"], - } - del submission["medium"] - del submission["address"] - - -def login_id_thirdparty_from_phone(identifier): - """ - Convert a phone login identifier type to a generic threepid identifier - Args: - identifier(dict): Login identifier dict of type 'm.id.phone' - - Returns: Login identifier dict of type 'm.id.threepid' - """ - if "country" not in identifier or "number" not in identifier: - raise SynapseError(400, "Invalid phone-type identifier") - - msisdn = phone_number_to_msisdn(identifier["country"], identifier["number"]) - - return {"type": "m.id.thirdparty", "medium": "msisdn", "address": msisdn} - - class LoginRestServlet(RestServlet): PATTERNS = client_patterns("/login$", v1=True) CAS_TYPE = "m.login.cas" @@ -174,7 +141,8 @@ class LoginRestServlet(RestServlet): login_submission.get("address"), login_submission.get("user"), ) - login_submission_legacy_convert(login_submission) + # Convert deprecated authdict formats to the current scheme + client_dict_convert_legacy_fields_to_identifier(login_submission) if "identifier" not in login_submission: raise SynapseError(400, "Missing param: identifier") @@ -185,7 +153,7 @@ class LoginRestServlet(RestServlet): # convert phone type identifiers to generic threepids if identifier["type"] == "m.id.phone": - identifier = login_id_thirdparty_from_phone(identifier) + identifier = login_id_phone_to_thirdparty(identifier) # convert threepid identifiers to user IDs if identifier["type"] == "m.id.thirdparty":