Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Move and refactor LoginRestServlet helper methods #8182

Merged
merged 9 commits into from
Aug 28, 2020
1 change: 1 addition & 0 deletions changelog.d/8182.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Refactor some of `LoginRestServlet`'s helper methods, and move them to `AuthHandler` for easier reuse.
79 changes: 79 additions & 0 deletions synapse/handlers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,98 @@
from synapse.handlers.ui_auth import INTERACTIVE_AUTH_CHECKERS
from synapse.handlers.ui_auth.checkers import UserInteractiveAuthChecker
from synapse.http.server import finish_request, respond_with_html
from synapse.http.servlet import assert_params_in_dict
from synapse.http.site import SynapseRequest
from synapse.logging.context import defer_to_thread
from synapse.metrics.background_process_metrics import run_as_background_process
from synapse.module_api import ModuleApi
from synapse.types import Requester, UserID
from synapse.util import stringutils as stringutils
from synapse.util.msisdn import phone_number_to_msisdn
from synapse.util.threepids import canonicalise_email

from ._base import BaseHandler

logger = logging.getLogger(__name__)


def convert_client_dict_legacy_fields_to_identifier(
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
submission: Dict[str, Union[str, Dict]]
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
):
"""
Convert a legacy-formatted login submission to an identifier dict.

Legacy login submissions (used in both login and user-interactive authentication)
provide user-identifying information at the top-level instead.

This 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 format of the client dict is invalid
"""
if "user" in submission:
submission["identifier"] = {"type": "m.id.user", "user": submission.pop("user")}

if "medium" in submission and "address" in submission:
submission["identifier"] = {
"type": "m.id.thirdparty",
"medium": submission.pop("medium"),
"address": submission.pop("address"),
}

# We've converted valid, legacy login submissions to an identifier. If the
# dict still doesn't have an identifier, it's invalid
assert_params_in_dict(submission, required=["identifier"])

# 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]) -> Dict[str, str]:
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
"""
Convert a phone login identifier type to a generic threepid identifier.

Args:
identifier: Login identifier dict of type 'm.id.phone'

Returns:
An equivalent m.id.thirdparty identifier dict
"""
if "type" not in identifier:
raise SynapseError(
400, "Invalid phone-type identifier", errcode=Codes.MISSING_PARAM
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this check is duplicated.


if "country" not in identifier or (
# The specification requires a "phone" field, while Synapse used to require a "number"
# field. Accept both for backwards compatibility.
"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["number"])

# Convert user-provided phone number to a consistent representation
msisdn = phone_number_to_msisdn(identifier["country"], phone_number)

return {
"type": "m.id.thirdparty",
"medium": "msisdn",
"address": msisdn,
}


class AuthHandler(BaseHandler):
SESSION_EXPIRE_MS = 48 * 60 * 60 * 1000

Expand Down
59 changes: 6 additions & 53 deletions synapse/rest/client/v1/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@

from synapse.api.errors import Codes, LoginError, SynapseError
from synapse.api.ratelimiting import Ratelimiter
from synapse.handlers.auth import (
convert_client_dict_legacy_fields_to_identifier,
login_id_phone_to_thirdparty,
)
from synapse.http.server import finish_request
from synapse.http.servlet import (
RestServlet,
Expand All @@ -28,56 +32,11 @@
from synapse.rest.client.v2_alpha._base import client_patterns
from synapse.rest.well_known import WellKnownBuilder
from synapse.types import JsonDict, UserID
from synapse.util.msisdn import phone_number_to_msisdn
from synapse.util.threepids import canonicalise_email

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 (
# The specification requires a "phone" field, while Synapse used to require a "number"
# field. Accept both for backwards compatibility.
"phone" not in identifier
and "number" not in identifier
):
raise SynapseError(400, "Invalid phone-type identifier")

# Accept both "phone" and "number" as valid keys in m.id.phone
phone_number = identifier.get("phone", identifier["number"])

msisdn = phone_number_to_msisdn(identifier["country"], phone_number)

return {"type": "m.id.thirdparty", "medium": "msisdn", "address": msisdn}


class LoginRestServlet(RestServlet):
PATTERNS = client_patterns("/login$", v1=True)
CAS_TYPE = "m.login.cas"
Expand Down Expand Up @@ -194,18 +153,12 @@ async def _do_other_login(self, login_submission: JsonDict) -> Dict[str, str]:
login_submission.get("address"),
login_submission.get("user"),
)
login_submission_legacy_convert(login_submission)

if "identifier" not in login_submission:
raise SynapseError(400, "Missing param: identifier")

convert_client_dict_legacy_fields_to_identifier(login_submission)
identifier = login_submission["identifier"]
if "type" not in identifier:
raise SynapseError(400, "Login identifier has no type")

# 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":
Expand Down