lookupv2servlet.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2019 The Matrix.org Foundation C.I.C.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. from __future__ import absolute_import
  16. from twisted.web.resource import Resource
  17. import logging
  18. from sydent.http.servlets import get_args, jsonwrap, send_cors
  19. from sydent.db.threepid_associations import GlobalAssociationStore
  20. from sydent.http.auth import authIfV2
  21. from sydent.http.servlets.hashdetailsservlet import HashDetailsServlet
  22. logger = logging.getLogger(__name__)
  23. class LookupV2Servlet(Resource):
  24. isLeaf = True
  25. def __init__(self, syd, lookup_pepper):
  26. self.sydent = syd
  27. self.globalAssociationStore = GlobalAssociationStore(self.sydent)
  28. self.lookup_pepper = lookup_pepper
  29. @jsonwrap
  30. def render_POST(self, request):
  31. """
  32. Perform lookups with potentially hashed 3PID details.
  33. Depending on our response to /hash_details, the client will choose a
  34. hash algorithm and pepper, hash the 3PIDs it wants to lookup, and
  35. send them to us, along with the algorithm and pepper it used.
  36. We first check this algorithm/pepper combo matches what we expect,
  37. then compare the 3PID details to what we have in the database.
  38. Params: A JSON object containing the following keys:
  39. * 'addresses': List of hashed/plaintext (depending on the
  40. algorithm) 3PID addresses and mediums.
  41. * 'algorithm': The algorithm the client has used to process
  42. the 3PIDs.
  43. * 'pepper': The pepper the client has attached to the 3PIDs.
  44. Returns: Object with key 'mappings', which is a dictionary of results
  45. where each result is a key/value pair of what the client sent, and
  46. the matching Matrix User ID that claims to own that 3PID.
  47. User IDs for which no mapping is found are omitted.
  48. """
  49. send_cors(request)
  50. authIfV2(self.sydent, request)
  51. args = get_args(request, ('addresses', 'algorithm', 'pepper'))
  52. addresses = args['addresses']
  53. if not isinstance(addresses, list):
  54. request.setResponseCode(400)
  55. return {'errcode': 'M_INVALID_PARAM', 'error': 'addresses must be a list'}
  56. algorithm = str(args['algorithm'])
  57. if algorithm not in HashDetailsServlet.known_algorithms:
  58. request.setResponseCode(400)
  59. return {'errcode': 'M_INVALID_PARAM', 'error': 'algorithm is not supported'}
  60. # Ensure address count is under the configured limit
  61. limit = int(self.sydent.cfg.get("general", "address_lookup_limit"))
  62. if len(addresses) > limit:
  63. request.setResponseCode(400)
  64. return {'errcode': 'M_TOO_LARGE', 'error': 'More than the maximum amount of '
  65. 'addresses provided'}
  66. pepper = str(args['pepper'])
  67. if pepper != self.lookup_pepper:
  68. request.setResponseCode(400)
  69. return {
  70. 'errcode': 'M_INVALID_PEPPER',
  71. 'error': "pepper does not match '%s'" % (self.lookup_pepper,),
  72. 'algorithm': algorithm,
  73. 'lookup_pepper': self.lookup_pepper,
  74. }
  75. logger.info("Lookup of %d threepid(s) with algorithm %s", len(addresses), algorithm)
  76. if algorithm == "none":
  77. # Lookup without hashing
  78. medium_address_tuples = []
  79. for address_and_medium in addresses:
  80. # Parse medium, address components
  81. address_medium_split = address_and_medium.split()
  82. # Forbid addresses that contain a space
  83. if len(address_medium_split) != 2:
  84. request.setResponseCode(400)
  85. return {
  86. 'errcode': 'M_UNKNOWN',
  87. 'error': 'Invalid "address medium" pair: "%s"' % address_and_medium
  88. }
  89. # Get the mxid for the address/medium combo if known
  90. address, medium = address_medium_split
  91. medium_address_tuples.append((medium, address))
  92. # Lookup the mxids
  93. medium_address_mxid_tuples = self.globalAssociationStore.getMxids(medium_address_tuples)
  94. # Return a dictionary of lookup_string: mxid values
  95. return {'mappings': {"%s %s" % (x[1], x[0]): x[2]
  96. for x in medium_address_mxid_tuples}}
  97. elif algorithm == "sha256":
  98. # Lookup using SHA256 with URL-safe base64 encoding
  99. mappings = self.globalAssociationStore.retrieveMxidsForHashes(addresses)
  100. return {'mappings': mappings}
  101. request.setResponseCode(400)
  102. return {'errcode': 'M_INVALID_PARAM', 'error': 'algorithm is not supported'}
  103. def render_OPTIONS(self, request):
  104. send_cors(request)
  105. return b''