233 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			233 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Python
		
	
	
| # -*- coding: utf-8 -*-
 | |
| # Copyright 2015, 2016 OpenMarket Ltd
 | |
| #
 | |
| # Licensed under the Apache License, Version 2.0 (the "License");
 | |
| # you may not use this file except in compliance with the License.
 | |
| # You may obtain a copy of the License at
 | |
| #
 | |
| #     http://www.apache.org/licenses/LICENSE-2.0
 | |
| #
 | |
| # Unless required by applicable law or agreed to in writing, software
 | |
| # distributed under the License is distributed on an "AS IS" BASIS,
 | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| # See the License for the specific language governing permissions and
 | |
| # limitations under the License.
 | |
| 
 | |
| import logging
 | |
| 
 | |
| from twisted.internet import defer
 | |
| 
 | |
| from synapse.api.errors import SynapseError
 | |
| from synapse.http.servlet import (
 | |
|     RestServlet, parse_json_object_from_request, parse_integer
 | |
| )
 | |
| from synapse.http.servlet import parse_string
 | |
| from synapse.types import StreamToken
 | |
| from ._base import client_v2_patterns
 | |
| 
 | |
| logger = logging.getLogger(__name__)
 | |
| 
 | |
| 
 | |
| class KeyUploadServlet(RestServlet):
 | |
|     """
 | |
|     POST /keys/upload HTTP/1.1
 | |
|     Content-Type: application/json
 | |
| 
 | |
|     {
 | |
|       "device_keys": {
 | |
|         "user_id": "<user_id>",
 | |
|         "device_id": "<device_id>",
 | |
|         "valid_until_ts": <millisecond_timestamp>,
 | |
|         "algorithms": [
 | |
|           "m.olm.curve25519-aes-sha256",
 | |
|         ]
 | |
|         "keys": {
 | |
|           "<algorithm>:<device_id>": "<key_base64>",
 | |
|         },
 | |
|         "signatures:" {
 | |
|           "<user_id>" {
 | |
|             "<algorithm>:<device_id>": "<signature_base64>"
 | |
|       } } },
 | |
|       "one_time_keys": {
 | |
|         "<algorithm>:<key_id>": "<key_base64>"
 | |
|       },
 | |
|     }
 | |
|     """
 | |
|     PATTERNS = client_v2_patterns("/keys/upload(/(?P<device_id>[^/]+))?$")
 | |
| 
 | |
|     def __init__(self, hs):
 | |
|         """
 | |
|         Args:
 | |
|             hs (synapse.server.HomeServer): server
 | |
|         """
 | |
|         super(KeyUploadServlet, self).__init__()
 | |
|         self.auth = hs.get_auth()
 | |
|         self.e2e_keys_handler = hs.get_e2e_keys_handler()
 | |
| 
 | |
|     @defer.inlineCallbacks
 | |
|     def on_POST(self, request, device_id):
 | |
|         requester = yield self.auth.get_user_by_req(request, allow_guest=True)
 | |
|         user_id = requester.user.to_string()
 | |
|         body = parse_json_object_from_request(request)
 | |
| 
 | |
|         if device_id is not None:
 | |
|             # passing the device_id here is deprecated; however, we allow it
 | |
|             # for now for compatibility with older clients.
 | |
|             if (requester.device_id is not None and
 | |
|                     device_id != requester.device_id):
 | |
|                 logger.warning("Client uploading keys for a different device "
 | |
|                                "(logged in as %s, uploading for %s)",
 | |
|                                requester.device_id, device_id)
 | |
|         else:
 | |
|             device_id = requester.device_id
 | |
| 
 | |
|         if device_id is None:
 | |
|             raise SynapseError(
 | |
|                 400,
 | |
|                 "To upload keys, you must pass device_id when authenticating"
 | |
|             )
 | |
| 
 | |
|         result = yield self.e2e_keys_handler.upload_keys_for_user(
 | |
|             user_id, device_id, body
 | |
|         )
 | |
|         defer.returnValue((200, result))
 | |
| 
 | |
| 
 | |
| class KeyQueryServlet(RestServlet):
 | |
|     """
 | |
|     POST /keys/query HTTP/1.1
 | |
|     Content-Type: application/json
 | |
|     {
 | |
|       "device_keys": {
 | |
|         "<user_id>": ["<device_id>"]
 | |
|     } }
 | |
| 
 | |
|     HTTP/1.1 200 OK
 | |
|     {
 | |
|       "device_keys": {
 | |
|         "<user_id>": {
 | |
|           "<device_id>": {
 | |
|             "user_id": "<user_id>", // Duplicated to be signed
 | |
|             "device_id": "<device_id>", // Duplicated to be signed
 | |
|             "valid_until_ts": <millisecond_timestamp>,
 | |
|             "algorithms": [ // List of supported algorithms
 | |
|               "m.olm.curve25519-aes-sha256",
 | |
|             ],
 | |
|             "keys": { // Must include a ed25519 signing key
 | |
|               "<algorithm>:<key_id>": "<key_base64>",
 | |
|             },
 | |
|             "signatures:" {
 | |
|               // Must be signed with device's ed25519 key
 | |
|               "<user_id>/<device_id>": {
 | |
|                 "<algorithm>:<key_id>": "<signature_base64>"
 | |
|               }
 | |
|               // Must be signed by this server.
 | |
|               "<server_name>": {
 | |
|                 "<algorithm>:<key_id>": "<signature_base64>"
 | |
|     } } } } } }
 | |
|     """
 | |
| 
 | |
|     PATTERNS = client_v2_patterns("/keys/query$")
 | |
| 
 | |
|     def __init__(self, hs):
 | |
|         """
 | |
|         Args:
 | |
|             hs (synapse.server.HomeServer):
 | |
|         """
 | |
|         super(KeyQueryServlet, self).__init__()
 | |
|         self.auth = hs.get_auth()
 | |
|         self.e2e_keys_handler = hs.get_e2e_keys_handler()
 | |
| 
 | |
|     @defer.inlineCallbacks
 | |
|     def on_POST(self, request):
 | |
|         yield self.auth.get_user_by_req(request, allow_guest=True)
 | |
|         timeout = parse_integer(request, "timeout", 10 * 1000)
 | |
|         body = parse_json_object_from_request(request)
 | |
|         result = yield self.e2e_keys_handler.query_devices(body, timeout)
 | |
|         defer.returnValue((200, result))
 | |
| 
 | |
| 
 | |
| class KeyChangesServlet(RestServlet):
 | |
|     """Returns the list of changes of keys between two stream tokens (may return
 | |
|     spurious extra results, since we currently ignore the `to` param).
 | |
| 
 | |
|         GET /keys/changes?from=...&to=...
 | |
| 
 | |
|         200 OK
 | |
|         { "changed": ["@foo:example.com"] }
 | |
|     """
 | |
|     PATTERNS = client_v2_patterns("/keys/changes$")
 | |
| 
 | |
|     def __init__(self, hs):
 | |
|         """
 | |
|         Args:
 | |
|             hs (synapse.server.HomeServer):
 | |
|         """
 | |
|         super(KeyChangesServlet, self).__init__()
 | |
|         self.auth = hs.get_auth()
 | |
|         self.device_handler = hs.get_device_handler()
 | |
| 
 | |
|     @defer.inlineCallbacks
 | |
|     def on_GET(self, request):
 | |
|         requester = yield self.auth.get_user_by_req(request, allow_guest=True)
 | |
| 
 | |
|         from_token_string = parse_string(request, "from")
 | |
| 
 | |
|         # We want to enforce they do pass us one, but we ignore it and return
 | |
|         # changes after the "to" as well as before.
 | |
|         parse_string(request, "to")
 | |
| 
 | |
|         from_token = StreamToken.from_string(from_token_string)
 | |
| 
 | |
|         user_id = requester.user.to_string()
 | |
| 
 | |
|         results = yield self.device_handler.get_user_ids_changed(
 | |
|             user_id, from_token,
 | |
|         )
 | |
| 
 | |
|         defer.returnValue((200, results))
 | |
| 
 | |
| 
 | |
| class OneTimeKeyServlet(RestServlet):
 | |
|     """
 | |
|     POST /keys/claim HTTP/1.1
 | |
|     {
 | |
|       "one_time_keys": {
 | |
|         "<user_id>": {
 | |
|           "<device_id>": "<algorithm>"
 | |
|     } } }
 | |
| 
 | |
|     HTTP/1.1 200 OK
 | |
|     {
 | |
|       "one_time_keys": {
 | |
|         "<user_id>": {
 | |
|           "<device_id>": {
 | |
|             "<algorithm>:<key_id>": "<key_base64>"
 | |
|     } } } }
 | |
| 
 | |
|     """
 | |
|     PATTERNS = client_v2_patterns("/keys/claim$")
 | |
| 
 | |
|     def __init__(self, hs):
 | |
|         super(OneTimeKeyServlet, self).__init__()
 | |
|         self.auth = hs.get_auth()
 | |
|         self.e2e_keys_handler = hs.get_e2e_keys_handler()
 | |
| 
 | |
|     @defer.inlineCallbacks
 | |
|     def on_POST(self, request):
 | |
|         yield self.auth.get_user_by_req(request, allow_guest=True)
 | |
|         timeout = parse_integer(request, "timeout", 10 * 1000)
 | |
|         body = parse_json_object_from_request(request)
 | |
|         result = yield self.e2e_keys_handler.claim_one_time_keys(
 | |
|             body,
 | |
|             timeout,
 | |
|         )
 | |
|         defer.returnValue((200, result))
 | |
| 
 | |
| 
 | |
| def register_servlets(hs, http_server):
 | |
|     KeyUploadServlet(hs).register(http_server)
 | |
|     KeyQueryServlet(hs).register(http_server)
 | |
|     KeyChangesServlet(hs).register(http_server)
 | |
|     OneTimeKeyServlet(hs).register(http_server)
 |