registerservlet.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. # Copyright 2019 The Matrix.org Foundation C.I.C.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import logging
  15. import urllib
  16. from http import HTTPStatus
  17. from typing import TYPE_CHECKING, Dict
  18. from twisted.internet.error import ConnectError, DNSLookupError
  19. from twisted.web.client import ResponseFailed
  20. from twisted.web.resource import Resource
  21. from twisted.web.server import Request
  22. from sydent.http.httpclient import FederationHttpClient
  23. from sydent.http.servlets import asyncjsonwrap, get_args, send_cors
  24. from sydent.types import JsonDict
  25. from sydent.users.tokens import issueToken
  26. from sydent.util.stringutils import is_valid_matrix_server_name
  27. if TYPE_CHECKING:
  28. from sydent.sydent import Sydent
  29. logger = logging.getLogger(__name__)
  30. class RegisterServlet(Resource):
  31. isLeaf = True
  32. def __init__(self, syd: "Sydent") -> None:
  33. self.sydent = syd
  34. self.client = FederationHttpClient(self.sydent)
  35. @asyncjsonwrap
  36. async def render_POST(self, request: Request) -> JsonDict:
  37. """
  38. Register with the Identity Server
  39. """
  40. send_cors(request)
  41. args = get_args(request, ("matrix_server_name", "access_token"))
  42. matrix_server = args["matrix_server_name"].lower()
  43. if not is_valid_matrix_server_name(matrix_server):
  44. request.setResponseCode(400)
  45. return {
  46. "errcode": "M_INVALID_PARAM",
  47. "error": "matrix_server_name must be a valid Matrix server name (IP address or hostname)",
  48. }
  49. def federation_request_problem(error: str) -> Dict[str, str]:
  50. logger.warning(error)
  51. request.setResponseCode(HTTPStatus.INTERNAL_SERVER_ERROR)
  52. return {
  53. "errcode": "M_UNKNOWN",
  54. "error": error,
  55. }
  56. try:
  57. result = await self.client.get_json(
  58. "matrix://%s/_matrix/federation/v1/openid/userinfo?access_token=%s"
  59. % (
  60. matrix_server,
  61. urllib.parse.quote(args["access_token"]),
  62. ),
  63. 1024 * 5,
  64. )
  65. except (DNSLookupError, ConnectError, ResponseFailed) as e:
  66. return federation_request_problem(
  67. f"Unable to contact the Matrix homeserver ({type(e).__name__})"
  68. )
  69. if "sub" not in result:
  70. return federation_request_problem(
  71. "The Matrix homeserver did not include 'sub' in its response",
  72. )
  73. user_id = result["sub"]
  74. if not isinstance(user_id, str):
  75. return federation_request_problem(
  76. "The Matrix homeserver returned a malformed reply"
  77. )
  78. user_id_components = user_id.split(":", 1)
  79. # Ensure there's a localpart and domain in the returned user ID.
  80. if len(user_id_components) != 2:
  81. return federation_request_problem(
  82. "The Matrix homeserver returned an invalid MXID"
  83. )
  84. user_id_server = user_id_components[1]
  85. if not is_valid_matrix_server_name(user_id_server):
  86. return federation_request_problem(
  87. "The Matrix homeserver returned an invalid MXID"
  88. )
  89. if user_id_server != matrix_server:
  90. return federation_request_problem(
  91. "The Matrix homeserver returned a MXID belonging to another homeserver"
  92. )
  93. tok = issueToken(self.sydent, user_id)
  94. # XXX: `token` is correct for the spec, but we released with `access_token`
  95. # for a substantial amount of time. Serve both to make spec-compliant clients
  96. # happy.
  97. return {
  98. "access_token": tok,
  99. "token": tok,
  100. }
  101. def render_OPTIONS(self, request: Request) -> bytes:
  102. send_cors(request)
  103. return b""