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

Commit

Permalink
Move and refactor LoginRestServlet helper methods (#8182)
Browse files Browse the repository at this point in the history
This is split out from #7438, which had gotten rather large.

`LoginRestServlet` has a couple helper methods, `login_submission_legacy_convert` and `login_id_thirdparty_from_phone`. They're primarily used for converting legacy user login submissions to "identifier" dicts ([see spec](https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-login)). Identifying information such as usernames or 3PID information used to be top-level in the login body. They're now supposed to be put inside an [identifier](https://matrix.org/docs/spec/client_server/r0.6.1#identifier-types) parameter instead.

#7438's purpose is to allow using the new identifier parameter during User-Interactive Authentication, which is currently handled in AuthHandler. That's why I've moved these helper methods there. I also moved the refactoring of these method from #7438 as they're relevant.
  • Loading branch information
anoadragon453 authored Aug 28, 2020
1 parent e00816a commit 2c2e649
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 55 deletions.
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.
88 changes: 87 additions & 1 deletion synapse/handlers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,101 @@
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.types import JsonDict, 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(
submission: JsonDict,
) -> Dict[str, str]:
"""
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.
These are 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
Returns:
The matching identifier dict
Raises:
SynapseError: If the format of the client dict is invalid
"""
identifier = submission.get("identifier", {})

# Generate an m.id.user identifier if "user" parameter is present
user = submission.get("user")
if user:
identifier = {"type": "m.id.user", "user": user}

# Generate an m.id.thirdparty identifier if "medium" and "address" parameters are present
medium = submission.get("medium")
address = submission.get("address")
if medium and address:
identifier = {
"type": "m.id.thirdparty",
"medium": medium,
"address": address,
}

# We've converted valid, legacy login submissions to an identifier. If the
# submission still doesn't have an identifier, it's invalid
if not identifier:
raise SynapseError(400, "Invalid login submission", Codes.INVALID_PARAM)

# Ensure the identifier has a type
if "type" not in identifier:
raise SynapseError(
400, "'identifier' dict has no key 'type'", errcode=Codes.MISSING_PARAM,
)

return identifier


def login_id_phone_to_thirdparty(identifier: JsonDict) -> Dict[str, str]:
"""
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 "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
60 changes: 6 additions & 54 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,11 @@ 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")

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

# 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

0 comments on commit 2c2e649

Please sign in to comment.