From 9a93e83d9007706df019f6f3b046e9332f3e9340 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Tue, 26 Aug 2014 16:26:30 +0100 Subject: [PATCH 1/7] Respect 'limit' param in initialSync api --- synapse/handlers/room.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index c2b10f418..5a4569ac9 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -270,6 +270,10 @@ class MessageHandler(BaseHandler): # FIXME (erikj): We need to not generate this token, now_token = "%s_%s" % (now_rooms_token, now_presence_token) + limit = pagin_config.limit + if not limit: + limit = 10 + for event in room_list: d = { "room_id": event.room_id, @@ -286,7 +290,7 @@ class MessageHandler(BaseHandler): try: messages, token = yield self.store.get_recent_events_for_room( event.room_id, - limit=10, + limit=limit, end_token=now_rooms_token, ) From ad6d5ac06c25ee9ea3d7ffb5fee6375a13727571 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 26 Aug 2014 17:00:24 +0100 Subject: [PATCH 2/7] Added RoomSendEventRestServlet to send generic non-state events. It even appears to work..! --- synapse/rest/room.py | 52 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/synapse/rest/room.py b/synapse/rest/room.py index b031ad1bc..27a622b57 100644 --- a/synapse/rest/room.py +++ b/synapse/rest/room.py @@ -168,6 +168,46 @@ class RoomStateEventRestServlet(RestServlet): defer.returnValue((200, "")) +class RoomSendEventRestServlet(RestServlet): + + def register(self, http_server): + # /rooms/$roomid/send/$event_type[/$txn_id] + PATTERN = ("/rooms/(?P[^/]*)/send/(?P[^/]*)") + register_txn_path(self, PATTERN, http_server, with_get=True) + + @defer.inlineCallbacks + def on_POST(self, request, room_id, event_type): + user = yield self.auth.get_user_by_req(request) + content = _parse_json(request) + + event = self.event_factory.create_event( + etype=event_type, + room_id=urllib.unquote(room_id), + user_id=user.to_string(), + content=content + ) + + msg_handler = self.handlers.message_handler + yield msg_handler.send_message(event) + + defer.returnValue((200, {"event_id": event.event_id})) + + def on_GET(self, request, room_id, event_type, txn_id): + return (200, "Not implemented") + + @defer.inlineCallbacks + def on_PUT(self, request, room_id, event_type, txn_id): + try: + defer.returnValue(self.txns.get_client_transaction(request, txn_id)) + except KeyError: + pass + + response = yield self.on_POST(request, room_id, event_type) + + self.txns.store_client_transaction(request, txn_id, response) + defer.returnValue(response) + + class JoinRoomAliasServlet(RestServlet): PATTERN = client_path_pattern("/join/(?P[^/]+)$") @@ -402,7 +442,7 @@ class RoomMembershipRestServlet(RestServlet): def on_PUT(self, request, room_id, membership_action, txn_id): try: defer.returnValue(self.txns.get_client_transaction(request, txn_id)) - except: + except KeyError: pass response = yield self.on_POST(request, room_id, membership_action) @@ -422,7 +462,7 @@ def _parse_json(request): raise SynapseError(400, "Content not JSON.", errcode=Codes.NOT_JSON) -def register_txn_path(servlet, regex_string, http_server): +def register_txn_path(servlet, regex_string, http_server, with_get=False): """Registers a transaction-based path. This registers two paths: @@ -433,6 +473,7 @@ def register_txn_path(servlet, regex_string, http_server): regex_string (str): The regex string to register. Must NOT have a trailing $ as this string will be appended to. http_server : The http_server to register paths with. + with_get: True to also register respective GET paths for the PUTs. """ http_server.register_path( "POST", @@ -444,6 +485,12 @@ def register_txn_path(servlet, regex_string, http_server): client_path_pattern(regex_string + "/(?P[^/]*)$"), servlet.on_PUT ) + if with_get: + http_server.register_path( + "GET", + client_path_pattern(regex_string + "/(?P[^/]*)$"), + servlet.on_GET + ) def register_servlets(hs, http_server): @@ -456,3 +503,4 @@ def register_servlets(hs, http_server): JoinRoomAliasServlet(hs).register(http_server) RoomTriggerBackfill(hs).register(http_server) RoomMembershipRestServlet(hs).register(http_server) + RoomSendEventRestServlet(hs).register(http_server) From 5a93bfe1f03bf225bcf3b14089faefa3b5715499 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 26 Aug 2014 17:21:48 +0100 Subject: [PATCH 3/7] Removed MessageRestServlet, use RoomSendEventRestServlet instead. Updated cmdclient, tests and webclient. All appears to work. --- cmdclient/console.py | 7 ++- synapse/rest/room.py | 49 ------------------- tests/rest/test_events.py | 2 +- tests/rest/test_rooms.py | 35 +++++-------- tests/rest/utils.py | 8 +-- webclient/components/matrix/matrix-service.js | 11 ++--- 6 files changed, 25 insertions(+), 87 deletions(-) diff --git a/cmdclient/console.py b/cmdclient/console.py index f997b7539..4cb604e79 100755 --- a/cmdclient/console.py +++ b/cmdclient/console.py @@ -409,10 +409,9 @@ class SynapseCmd(cmd.Cmd): def do_send(self, line): """Sends a message. "send " """ args = self._parse(line, ["roomid", "body"]) - msg_id = "m%s" % int(time.time()) - path = "/rooms/%s/messages/%s/%s" % (urllib.quote(args["roomid"]), - self._usr(), - msg_id) + txn_id = "txn%s" % int(time.time()) + path = "/rooms/%s/send/m.room.message/%s" % (urllib.quote(args["roomid"]), + txn_id) body_json = { "msgtype": "m.text", "body": args["body"] diff --git a/synapse/rest/room.py b/synapse/rest/room.py index 27a622b57..e00f74cf1 100644 --- a/synapse/rest/room.py +++ b/synapse/rest/room.py @@ -228,54 +228,6 @@ class JoinRoomAliasServlet(RestServlet): defer.returnValue((200, ret_dict)) -class MessageRestServlet(RestServlet): - PATTERN = client_path_pattern("/rooms/(?P[^/]*)/messages/" - + "(?P[^/]*)/(?P[^/]*)$") - - def get_event_type(self): - return MessageEvent.TYPE - - @defer.inlineCallbacks - def on_GET(self, request, room_id, sender_id, msg_id): - user = yield self.auth.get_user_by_req(request) - - msg_handler = self.handlers.message_handler - msg = yield msg_handler.get_message(room_id=urllib.unquote(room_id), - sender_id=urllib.unquote(sender_id), - msg_id=msg_id, - user_id=user.to_string(), - ) - - if not msg: - raise SynapseError(404, "Message not found.", - errcode=Codes.NOT_FOUND) - - defer.returnValue((200, json.loads(msg.content))) - - @defer.inlineCallbacks - def on_PUT(self, request, room_id, sender_id, msg_id): - user = yield self.auth.get_user_by_req(request) - - if user.to_string() != urllib.unquote(sender_id): - raise SynapseError(403, "Must send messages as yourself.", - errcode=Codes.FORBIDDEN) - - content = _parse_json(request) - - event = self.event_factory.create_event( - etype=self.get_event_type(), - room_id=urllib.unquote(room_id), - user_id=user.to_string(), - msg_id=msg_id, - content=content - ) - - msg_handler = self.handlers.message_handler - yield msg_handler.send_message(event) - - defer.returnValue((200, "")) - - class FeedbackRestServlet(RestServlet): PATTERN = client_path_pattern( "/rooms/(?P[^/]*)/messages/" + @@ -495,7 +447,6 @@ def register_txn_path(servlet, regex_string, http_server, with_get=False): def register_servlets(hs, http_server): RoomStateEventRestServlet(hs).register(http_server) - MessageRestServlet(hs).register(http_server) FeedbackRestServlet(hs).register(http_server) RoomCreateRestServlet(hs).register(http_server) RoomMemberListRestServlet(hs).register(http_server) diff --git a/tests/rest/test_events.py b/tests/rest/test_events.py index 4025e1458..7bc05dc2b 100644 --- a/tests/rest/test_events.py +++ b/tests/rest/test_events.py @@ -181,7 +181,7 @@ class EventStreamPermissionsTestCase(RestTestCase): room_id = "!rid1:test" yield self.create_room_as(room_id, self.other_user, tok=self.other_token) - yield self.send(room_id, self.other_user, tok=self.other_token) + yield self.send(room_id, tok=self.other_token) # invited to room (expect no content for room) yield self.invite(room_id, src=self.other_user, targ=self.user_id, diff --git a/tests/rest/test_rooms.py b/tests/rest/test_rooms.py index 8514d6ba2..f18c506a7 100644 --- a/tests/rest/test_rooms.py +++ b/tests/rest/test_rooms.py @@ -83,8 +83,8 @@ class RoomPermissionsTestCase(RestTestCase): is_public=True) # send a message in one of the rooms - self.created_rmid_msg_path = ("/rooms/%s/messages/%s/midaaa1" % - (self.created_rmid, self.rmcreator_id)) + self.created_rmid_msg_path = ("/rooms/%s/send/m.room.message/a1" % + (self.created_rmid)) (code, response) = yield self.mock_resource.trigger( "PUT", self.created_rmid_msg_path, @@ -138,14 +138,14 @@ class RoomPermissionsTestCase(RestTestCase): @defer.inlineCallbacks def test_send_message(self): msg_content = '{"msgtype":"m.text","body":"hello"}' - send_msg_path = ("/rooms/%s/messages/%s/mid1" % - (self.created_rmid, self.user_id)) + send_msg_path = ("/rooms/%s/send/m.room.message/mid1" % + (self.created_rmid)) # send message in uncreated room, expect 403 (code, response) = yield self.mock_resource.trigger( "PUT", - "/rooms/%s/messages/%s/mid1" % - (self.uncreated_rmid, self.user_id), msg_content) + "/rooms/%s/send/m.room.message/mid2" % + (self.uncreated_rmid), msg_content) self.assertEquals(403, code, msg=str(response)) # send message in created room not joined (no state), expect 403 @@ -875,9 +875,8 @@ class RoomMessagesTestCase(RestTestCase): @defer.inlineCallbacks def test_invalid_puts(self): - path = "/rooms/%s/messages/%s/mid1" % ( - urllib.quote(self.room_id), self.user_id - ) + path = "/rooms/%s/send/m.room.message/mid1" % ( + urllib.quote(self.room_id)) # missing keys or invalid json (code, response) = yield self.mock_resource.trigger("PUT", path, '{}') @@ -905,9 +904,8 @@ class RoomMessagesTestCase(RestTestCase): @defer.inlineCallbacks def test_rooms_messages_sent(self): - path = "/rooms/%s/messages/%s/mid1" % ( - urllib.quote(self.room_id), self.user_id - ) + path = "/rooms/%s/send/m.room.message/mid1" % ( + urllib.quote(self.room_id)) content = '{"body":"test","msgtype":{"type":"a"}}' (code, response) = yield self.mock_resource.trigger("PUT", path, content) @@ -923,9 +921,8 @@ class RoomMessagesTestCase(RestTestCase): # self.assert_dict(json.loads(content), response) # m.text message type - path = "/rooms/%s/messages/%s/mid2" % ( - urllib.quote(self.room_id), self.user_id - ) + path = "/rooms/%s/send/m.room.message/mid2" % ( + urllib.quote(self.room_id)) content = '{"body":"test2","msgtype":"m.text"}' (code, response) = yield self.mock_resource.trigger("PUT", path, content) self.assertEquals(200, code, msg=str(response)) @@ -933,11 +930,3 @@ class RoomMessagesTestCase(RestTestCase): # (code, response) = yield self.mock_resource.trigger("GET", path, None) # self.assertEquals(200, code, msg=str(response)) # self.assert_dict(json.loads(content), response) - - # trying to send message in different user path - path = "/rooms/%s/messages/%s/mid2" % ( - urllib.quote(self.room_id), "invalid" + self.user_id - ) - content = '{"body":"test2","msgtype":"m.text"}' - (code, response) = yield self.mock_resource.trigger("PUT", path, content) - self.assertEquals(403, code, msg=str(response)) diff --git a/tests/rest/utils.py b/tests/rest/utils.py index bc8bff0f1..590d12f15 100644 --- a/tests/rest/utils.py +++ b/tests/rest/utils.py @@ -99,14 +99,14 @@ class RestTestCase(unittest.TestCase): defer.returnValue(response) @defer.inlineCallbacks - def send(self, room_id, sender_id, body=None, msg_id=None, tok=None, + def send(self, room_id, body=None, txn_id=None, tok=None, expect_code=200): - if msg_id is None: - msg_id = "m%s" % (str(time.time())) + if txn_id is None: + txn_id = "m%s" % (str(time.time())) if body is None: body = "body_text_here" - path = "/rooms/%s/messages/%s/%s" % (room_id, sender_id, msg_id) + path = "/rooms/%s/send/m.room.message/%s" % (room_id, txn_id) content = '{"msgtype":"m.text","body":"%s"}' % body if tok: path = path + "?access_token=%s" % tok diff --git a/webclient/components/matrix/matrix-service.js b/webclient/components/matrix/matrix-service.js index fe5f120aa..228648560 100644 --- a/webclient/components/matrix/matrix-service.js +++ b/webclient/components/matrix/matrix-service.js @@ -162,12 +162,12 @@ angular.module('matrixService', []) return doRequest("GET", path, undefined, {}); }, - sendMessage: function(room_id, msg_id, content) { + sendMessage: function(room_id, txn_id, content) { // The REST path spec - var path = "/rooms/$room_id/messages/$from/$msg_id"; + var path = "/rooms/$room_id/send/m.room.message/$txn_id"; - if (!msg_id) { - msg_id = "m" + new Date().getTime(); + if (!txn_id) { + txn_id = "m" + new Date().getTime(); } // Like the cmd client, escape room ids @@ -175,8 +175,7 @@ angular.module('matrixService', []) // Customize it path = path.replace("$room_id", room_id); - path = path.replace("$from", config.user_id); - path = path.replace("$msg_id", msg_id); + path = path.replace("$txn_id", txn_id); return doRequest("PUT", path, undefined, content); }, From 5a3df1d0299296eaab051310678c1e8db2040ac1 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 26 Aug 2014 17:49:46 +0100 Subject: [PATCH 4/7] Feedback: Removed FeedbackRestServlet. Modified keys on FeedbackEvent. Expanded the feedback constants to fully explain what type of feedback they are. --- synapse/api/constants.py | 4 +-- synapse/api/events/room.py | 16 +++++---- synapse/rest/room.py | 70 ------------------------------------- synapse/storage/feedback.py | 4 +-- 4 files changed, 13 insertions(+), 81 deletions(-) diff --git a/synapse/api/constants.py b/synapse/api/constants.py index 2af542402..f69f2445a 100644 --- a/synapse/api/constants.py +++ b/synapse/api/constants.py @@ -31,8 +31,8 @@ class Feedback(object): """Represents the types of feedback a user can send in response to a message.""" - DELIVERED = u"d" - READ = u"r" + DELIVERED = u"delivered" + READ = u"read" LIST = (DELIVERED, READ) diff --git a/synapse/api/events/room.py b/synapse/api/events/room.py index 2a7b5e8ab..9faad57ac 100644 --- a/synapse/api/events/room.py +++ b/synapse/api/events/room.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from synapse.api.constants import Membership +from synapse.api.constants import Feedback, Membership from synapse.api.errors import SynapseError from . import SynapseEvent @@ -93,17 +93,19 @@ class MessageEvent(SynapseEvent): class FeedbackEvent(SynapseEvent): TYPE = "m.room.message.feedback" - valid_keys = SynapseEvent.valid_keys + [ - "msg_id", # the message ID being acknowledged - "msg_sender_id", # person who is sending the feedback is 'user_id' - "feedback_type", # the type of feedback (delivery, read, etc) - ] + valid_keys = SynapseEvent.valid_keys def __init__(self, **kwargs): super(FeedbackEvent, self).__init__(**kwargs) + if not kwargs["content"]["type"] in Feedback.LIST: + raise SynapseError(400, "Bad feedback value.") def get_content_template(self): - return {} + return { + "type": u"string", + "target_event_id": u"string", + "msg_sender_id": u"string" + } class InviteJoinEvent(SynapseEvent): diff --git a/synapse/rest/room.py b/synapse/rest/room.py index e00f74cf1..a98c7b5c1 100644 --- a/synapse/rest/room.py +++ b/synapse/rest/room.py @@ -228,75 +228,6 @@ class JoinRoomAliasServlet(RestServlet): defer.returnValue((200, ret_dict)) -class FeedbackRestServlet(RestServlet): - PATTERN = client_path_pattern( - "/rooms/(?P[^/]*)/messages/" + - "(?P[^/]*)/(?P[^/]*)/feedback/" + - "(?P[^/]*)/(?P[^/]*)$" - ) - - def get_event_type(self): - return FeedbackEvent.TYPE - - @defer.inlineCallbacks - def on_GET(self, request, room_id, msg_sender_id, msg_id, fb_sender_id, - feedback_type): - yield (self.auth.get_user_by_req(request)) - - # TODO (erikj): Implement this? - raise NotImplementedError("Getting feedback is not supported") - -# if feedback_type not in Feedback.LIST: -# raise SynapseError(400, "Bad feedback type.", -# errcode=Codes.BAD_JSON) -# -# msg_handler = self.handlers.message_handler -# feedback = yield msg_handler.get_feedback( -# room_id=urllib.unquote(room_id), -# msg_sender_id=msg_sender_id, -# msg_id=msg_id, -# user_id=user.to_string(), -# fb_sender_id=fb_sender_id, -# fb_type=feedback_type -# ) -# -# if not feedback: -# raise SynapseError(404, "Feedback not found.", -# errcode=Codes.NOT_FOUND) -# -# defer.returnValue((200, json.loads(feedback.content))) - - @defer.inlineCallbacks - def on_PUT(self, request, room_id, sender_id, msg_id, fb_sender_id, - feedback_type): - user = yield (self.auth.get_user_by_req(request)) - - if user.to_string() != fb_sender_id: - raise SynapseError(403, "Must send feedback as yourself.", - errcode=Codes.FORBIDDEN) - - if feedback_type not in Feedback.LIST: - raise SynapseError(400, "Bad feedback type.", - errcode=Codes.BAD_JSON) - - content = _parse_json(request) - - event = self.event_factory.create_event( - etype=self.get_event_type(), - room_id=urllib.unquote(room_id), - msg_sender_id=sender_id, - msg_id=msg_id, - user_id=user.to_string(), # user sending the feedback - feedback_type=feedback_type, - content=content - ) - - msg_handler = self.handlers.message_handler - yield msg_handler.send_feedback(event) - - defer.returnValue((200, "")) - - class RoomMemberListRestServlet(RestServlet): PATTERN = client_path_pattern("/rooms/(?P[^/]*)/members$") @@ -447,7 +378,6 @@ def register_txn_path(servlet, regex_string, http_server, with_get=False): def register_servlets(hs, http_server): RoomStateEventRestServlet(hs).register(http_server) - FeedbackRestServlet(hs).register(http_server) RoomCreateRestServlet(hs).register(http_server) RoomMemberListRestServlet(hs).register(http_server) RoomMessageListRestServlet(hs).register(http_server) diff --git a/synapse/storage/feedback.py b/synapse/storage/feedback.py index cdc667011..513b72d27 100644 --- a/synapse/storage/feedback.py +++ b/synapse/storage/feedback.py @@ -23,9 +23,9 @@ class FeedbackStore(SQLBaseStore): def _store_feedback(self, event): return self._simple_insert("feedback", { "event_id": event.event_id, - "feedback_type": event.feedback_type, + "feedback_type": event.content["type"], "room_id": event.room_id, - "target_event_id": event.target_event, + "target_event_id": event.content["target_event_id"], "sender": event.user_id, }) From f84ddc75cbe16e01107e70de9609639816d36320 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 26 Aug 2014 17:54:18 +0100 Subject: [PATCH 5/7] Pepper UT TODOs --- synapse/rest/initial_sync.py | 1 + synapse/rest/room.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/synapse/rest/initial_sync.py b/synapse/rest/initial_sync.py index fab748f56..ce7937a91 100644 --- a/synapse/rest/initial_sync.py +++ b/synapse/rest/initial_sync.py @@ -19,6 +19,7 @@ from synapse.api.streams import PaginationConfig from base import RestServlet, client_path_pattern +# TODO: Needs unit testing class InitialSyncRestServlet(RestServlet): PATTERN = client_path_pattern("/initialSync$") diff --git a/synapse/rest/room.py b/synapse/rest/room.py index a98c7b5c1..5eee49e51 100644 --- a/synapse/rest/room.py +++ b/synapse/rest/room.py @@ -96,6 +96,7 @@ class RoomCreateRestServlet(RestServlet): return (200, {}) +# TODO: Needs unit testing for generic events class RoomStateEventRestServlet(RestServlet): def register(self, http_server): # /room/$roomid/state/$eventtype @@ -168,6 +169,7 @@ class RoomStateEventRestServlet(RestServlet): defer.returnValue((200, "")) +# TODO: Needs unit testing for generic events + feedback class RoomSendEventRestServlet(RestServlet): def register(self, http_server): @@ -228,6 +230,7 @@ class JoinRoomAliasServlet(RestServlet): defer.returnValue((200, ret_dict)) +# TODO: Needs unit testing class RoomMemberListRestServlet(RestServlet): PATTERN = client_path_pattern("/rooms/(?P[^/]*)/members$") @@ -255,6 +258,7 @@ class RoomMemberListRestServlet(RestServlet): defer.returnValue((200, members)) +# TODO: Needs unit testing class RoomMessageListRestServlet(RestServlet): PATTERN = client_path_pattern("/rooms/(?P[^/]*)/messages$") @@ -289,6 +293,7 @@ class RoomTriggerBackfill(RestServlet): defer.returnValue((200, res)) +# TODO: Needs unit testing class RoomMembershipRestServlet(RestServlet): def register(self, http_server): From dfa0cd1d90cb80de7e42fc2c8aaac8911287d718 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 27 Aug 2014 09:43:42 +0100 Subject: [PATCH 6/7] Modified /join/$identifier to support $identifier being a room ID in addition to a room alias. --- synapse/api/auth.py | 2 ++ synapse/rest/room.py | 63 +++++++++++++++++++++++++++++++++++--------- synapse/server.py | 10 ++++++- 3 files changed, 61 insertions(+), 14 deletions(-) diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 15407df14..886e132e1 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -162,6 +162,8 @@ class Auth(object): """ try: user_id = yield self.store.get_user_by_token(token=token) + if not user_id: + raise StoreError() defer.returnValue(self.hs.parse_userid(user_id)) except StoreError: raise AuthError(403, "Unrecognised access token.", diff --git a/synapse/rest/room.py b/synapse/rest/room.py index 5eee49e51..b8d5cb87f 100644 --- a/synapse/rest/room.py +++ b/synapse/rest/room.py @@ -18,10 +18,8 @@ from twisted.internet import defer from base import RestServlet, client_path_pattern from synapse.api.errors import SynapseError, Codes -from synapse.api.events.room import ( - MessageEvent, RoomMemberEvent, FeedbackEvent -) -from synapse.api.constants import Feedback +from synapse.api.events.room import RoomMemberEvent +from synapse.api.constants import Membership from synapse.api.streams import PaginationConfig import json @@ -210,24 +208,63 @@ class RoomSendEventRestServlet(RestServlet): defer.returnValue(response) +# TODO: Needs unit testing for room ID + alias joins class JoinRoomAliasServlet(RestServlet): - PATTERN = client_path_pattern("/join/(?P[^/]+)$") + + def register(self, http_server): + # /join/$room_identifier[/$txn_id] + PATTERN = ("/join/(?P[^/]*)") + register_txn_path(self, PATTERN, http_server) @defer.inlineCallbacks - def on_PUT(self, request, room_alias): + def on_POST(self, request, room_identifier): user = yield self.auth.get_user_by_req(request) - if not user: - defer.returnValue((403, "Unrecognized user")) + # the identifier could be a room alias or a room id. Try one then the + # other if it fails to parse, without swallowing other valid + # SynapseErrors. - logger.debug("room_alias: %s", room_alias) + identifier = None + is_room_alias = False + try: + identifier = self.hs.parse_roomalias( + urllib.unquote(room_identifier) + ) + is_room_alias = True + except SynapseError: + identifier = self.hs.parse_roomid( + urllib.unquote(room_identifier) + ) - room_alias = self.hs.parse_roomalias(urllib.unquote(room_alias)) + # TODO: Support for specifying the home server to join with? - handler = self.handlers.room_member_handler - ret_dict = yield handler.join_room_alias(user, room_alias) + if is_room_alias: + handler = self.handlers.room_member_handler + ret_dict = yield handler.join_room_alias(user, identifier) + defer.returnValue((200, ret_dict)) + else: # room id + event = self.event_factory.create_event( + etype=RoomMemberEvent.TYPE, + content={"membership": Membership.JOIN}, + room_id=urllib.unquote(identifier.to_string()), + user_id=user.to_string(), + state_key=user.to_string() + ) + handler = self.handlers.room_member_handler + yield handler.change_membership(event) + defer.returnValue((200, "")) - defer.returnValue((200, ret_dict)) + @defer.inlineCallbacks + def on_PUT(self, request, room_identifier, txn_id): + try: + defer.returnValue(self.txns.get_client_transaction(request, txn_id)) + except KeyError: + pass + + response = yield self.on_POST(request, room_identifier) + + self.txns.store_client_transaction(request, txn_id, response) + defer.returnValue(response) # TODO: Needs unit testing diff --git a/synapse/server.py b/synapse/server.py index c5b0a3275..439b9d628 100644 --- a/synapse/server.py +++ b/synapse/server.py @@ -28,7 +28,7 @@ from synapse.handlers import Handlers from synapse.rest import RestServletFactory from synapse.state import StateHandler from synapse.storage import DataStore -from synapse.types import UserID, RoomAlias +from synapse.types import UserID, RoomAlias, RoomID from synapse.util import Clock from synapse.util.distributor import Distributor from synapse.util.lockutils import LockManager @@ -117,6 +117,9 @@ class BaseHomeServer(object): setattr(BaseHomeServer, "get_%s" % (depname), _get) + # TODO: Why are these parse_ methods so high up along with other globals? + # Surely these should be in a util package or in the api package? + # Other utility methods def parse_userid(self, s): """Parse the string given by 's' as a User ID and return a UserID @@ -128,6 +131,11 @@ class BaseHomeServer(object): object.""" return RoomAlias.from_string(s, hs=self) + def parse_roomid(self, s): + """Parse the string given by 's' as a Room ID and return a RoomID + object.""" + return RoomID.from_string(s, hs=self) + # Build magic accessors for every dependency for depname in BaseHomeServer.DEPENDENCIES: BaseHomeServer._make_dependency_method(depname) From c65885e1668d4a241a04a7cb3b970a0d6833a5ad Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 27 Aug 2014 10:33:01 +0100 Subject: [PATCH 7/7] Added support for GET /events/$eventid with auth checks. --- synapse/handlers/__init__.py | 3 ++- synapse/handlers/events.py | 26 ++++++++++++++++++++++++++ synapse/rest/events.py | 17 +++++++++++++++++ synapse/storage/__init__.py | 1 - 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/synapse/handlers/__init__.py b/synapse/handlers/__init__.py index 8a4aa6e5d..7417a02ce 100644 --- a/synapse/handlers/__init__.py +++ b/synapse/handlers/__init__.py @@ -17,7 +17,7 @@ from .register import RegistrationHandler from .room import ( MessageHandler, RoomCreationHandler, RoomMemberHandler, RoomListHandler ) -from .events import EventStreamHandler +from .events import EventStreamHandler, EventHandler from .federation import FederationHandler from .login import LoginHandler from .profile import ProfileHandler @@ -39,6 +39,7 @@ class Handlers(object): self.room_creation_handler = RoomCreationHandler(hs) self.room_member_handler = RoomMemberHandler(hs) self.event_stream_handler = EventStreamHandler(hs) + self.event_handler = EventHandler(hs) self.federation_handler = FederationHandler(hs) self.profile_handler = ProfileHandler(hs) self.presence_handler = PresenceHandler(hs) diff --git a/synapse/handlers/events.py b/synapse/handlers/events.py index 6bb797caf..1bd173acd 100644 --- a/synapse/handlers/events.py +++ b/synapse/handlers/events.py @@ -144,3 +144,29 @@ class EventStreamHandler(BaseHandler): self._stop_timer_per_user[auth_user] = ( self.clock.call_later(5, _later) ) + + +class EventHandler(BaseHandler): + + @defer.inlineCallbacks + def get_event(self, user, event_id): + """Retrieve a single specified event. + + Args: + user (synapse.types.UserID): The user requesting the event + event_id (str): The event ID to obtain. + Returns: + dict: An event, or None if there is no event matching this ID. + Raises: + SynapseError if there was a problem retrieving this event, or + AuthError if the user does not have the rights to inspect this + event. + """ + event = yield self.store.get_event(event_id) + + if not event: + defer.returnValue(None) + return + + yield self.auth.check(event, raises=True) + defer.returnValue(event) diff --git a/synapse/rest/events.py b/synapse/rest/events.py index ca2f6978e..d89dfc193 100644 --- a/synapse/rest/events.py +++ b/synapse/rest/events.py @@ -47,5 +47,22 @@ class EventStreamRestServlet(RestServlet): return (200, {}) +# TODO: Unit test gets, with and without auth, with different kinds of events. +class EventRestServlet(RestServlet): + PATTERN = client_path_pattern("/events/(?P[^/]*)$") + + @defer.inlineCallbacks + def on_GET(self, request, event_id): + auth_user = yield self.auth.get_user_by_req(request) + handler = self.handlers.event_handler + event = yield handler.get_event(auth_user, event_id) + + if event: + defer.returnValue((200, event.get_dict())) + else: + defer.returnValue((404, "Event not found.")) + + def register_servlets(hs, http_server): EventStreamRestServlet(hs).register(http_server) + EventRestServlet(hs).register(http_server) diff --git a/synapse/storage/__init__.py b/synapse/storage/__init__.py index a97a42e1e..38ab03c45 100644 --- a/synapse/storage/__init__.py +++ b/synapse/storage/__init__.py @@ -80,7 +80,6 @@ class DataStore(RoomMemberStore, RoomStore, [ "event_id", "type", - "sender", "room_id", "content", "unrecognized_keys"