summary refs log tree commit diff
path: root/synapse
diff options
context:
space:
mode:
authorMark Haines <mark.haines@matrix.org>2014-08-27 11:19:37 +0100
committerMark Haines <mark.haines@matrix.org>2014-08-27 11:19:37 +0100
commitbf05218c4b1740cdc5477cadbc36f0575e2dcd51 (patch)
treeced8ed09e18afb11e97734959150b8ef7f0db833 /synapse
parentFold federation/handler into handlers/federation (diff)
parentAdded support for GET /events/$eventid with auth checks. (diff)
downloadsynapse-bf05218c4b1740cdc5477cadbc36f0575e2dcd51.tar.xz
Merge branch 'develop' into storage_transactions
Diffstat (limited to 'synapse')
-rw-r--r--synapse/api/auth.py2
-rw-r--r--synapse/api/constants.py4
-rw-r--r--synapse/api/events/room.py16
-rw-r--r--synapse/handlers/__init__.py3
-rw-r--r--synapse/handlers/events.py26
-rw-r--r--synapse/handlers/room.py6
-rw-r--r--synapse/rest/events.py17
-rw-r--r--synapse/rest/initial_sync.py1
-rw-r--r--synapse/rest/room.py199
-rw-r--r--synapse/server.py10
-rw-r--r--synapse/storage/__init__.py1
-rw-r--r--synapse/storage/feedback.py4
12 files changed, 160 insertions, 129 deletions
diff --git a/synapse/api/auth.py b/synapse/api/auth.py
index 646f6dc06c..ef3604077d 100644
--- a/synapse/api/auth.py
+++ b/synapse/api/auth.py
@@ -168,6 +168,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/api/constants.py b/synapse/api/constants.py
index 2af5424029..f69f2445a2 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 2a7b5e8aba..9faad57ac0 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/handlers/__init__.py b/synapse/handlers/__init__.py
index 8a4aa6e5d6..7417a02cea 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 6bb797caf2..1bd173acd8 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/handlers/room.py b/synapse/handlers/room.py
index 6abfa00c5c..9b02ce0dfd 100644
--- a/synapse/handlers/room.py
+++ b/synapse/handlers/room.py
@@ -249,6 +249,10 @@ class MessageHandler(BaseRoomHandler):
         # 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,
@@ -265,7 +269,7 @@ class MessageHandler(BaseRoomHandler):
             try:
                 messages, token = yield self.store.get_recent_events_for_room(
                     event.room_id,
-                    limit=10,
+                    limit=limit,
                     end_token=now_rooms_token,
                 )
 
diff --git a/synapse/rest/events.py b/synapse/rest/events.py
index ca2f6978e5..d89dfc193c 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<event_id>[^/]*)$")
+
+    @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/rest/initial_sync.py b/synapse/rest/initial_sync.py
index fab748f562..ce7937a919 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 b031ad1bc4..b8d5cb87fd 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
@@ -96,6 +94,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,143 +167,107 @@ class RoomStateEventRestServlet(RestServlet):
             defer.returnValue((200, ""))
 
 
-class JoinRoomAliasServlet(RestServlet):
-    PATTERN = client_path_pattern("/join/(?P<room_alias>[^/]+)$")
-
-    @defer.inlineCallbacks
-    def on_PUT(self, request, room_alias):
-        user = yield self.auth.get_user_by_req(request)
-
-        if not user:
-            defer.returnValue((403, "Unrecognized user"))
-
-        logger.debug("room_alias: %s", room_alias)
-
-        room_alias = self.hs.parse_roomalias(urllib.unquote(room_alias))
-
-        handler = self.handlers.room_member_handler
-        ret_dict = yield handler.join_room_alias(user, room_alias)
+# TODO: Needs unit testing for generic events + feedback
+class RoomSendEventRestServlet(RestServlet):
 
-        defer.returnValue((200, ret_dict))
-
-
-class MessageRestServlet(RestServlet):
-    PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/messages/"
-                                  + "(?P<sender_id>[^/]*)/(?P<msg_id>[^/]*)$")
-
-    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)))
+    def register(self, http_server):
+        # /rooms/$roomid/send/$event_type[/$txn_id]
+        PATTERN = ("/rooms/(?P<room_id>[^/]*)/send/(?P<event_type>[^/]*)")
+        register_txn_path(self, PATTERN, http_server, with_get=True)
 
     @defer.inlineCallbacks
-    def on_PUT(self, request, room_id, sender_id, msg_id):
+    def on_POST(self, request, room_id, event_type):
         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(),
+            etype=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, ""))
+        defer.returnValue((200, {"event_id": event.event_id}))
 
+    def on_GET(self, request, room_id, event_type, txn_id):
+        return (200, "Not implemented")
 
-class FeedbackRestServlet(RestServlet):
-    PATTERN = client_path_pattern(
-        "/rooms/(?P<room_id>[^/]*)/messages/" +
-        "(?P<msg_sender_id>[^/]*)/(?P<msg_id>[^/]*)/feedback/" +
-        "(?P<sender_id>[^/]*)/(?P<feedback_type>[^/]*)$"
-    )
+    @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
 
-    def get_event_type(self):
-        return FeedbackEvent.TYPE
+        response = yield self.on_POST(request, room_id, event_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))
+        self.txns.store_client_transaction(request, txn_id, response)
+        defer.returnValue(response)
 
-        # 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)))
+# TODO: Needs unit testing for room ID + alias joins
+class JoinRoomAliasServlet(RestServlet):
+
+    def register(self, http_server):
+        # /join/$room_identifier[/$txn_id]
+        PATTERN = ("/join/(?P<room_identifier>[^/]*)")
+        register_txn_path(self, PATTERN, http_server)
 
     @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))
+    def on_POST(self, request, room_identifier):
+        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)
+        # 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.
 
-        if feedback_type not in Feedback.LIST:
-            raise SynapseError(400, "Bad feedback type.",
-                               errcode=Codes.BAD_JSON)
+        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)
+            )
 
-        content = _parse_json(request)
+        # TODO: Support for specifying the home server to join with?
 
-        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
+        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, ""))
 
-        msg_handler = self.handlers.message_handler
-        yield msg_handler.send_feedback(event)
+    @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
 
-        defer.returnValue((200, ""))
+        response = yield self.on_POST(request, room_identifier)
+
+        self.txns.store_client_transaction(request, txn_id, response)
+        defer.returnValue(response)
 
 
+# TODO: Needs unit testing
 class RoomMemberListRestServlet(RestServlet):
     PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/members$")
 
@@ -332,6 +295,7 @@ class RoomMemberListRestServlet(RestServlet):
         defer.returnValue((200, members))
 
 
+# TODO: Needs unit testing
 class RoomMessageListRestServlet(RestServlet):
     PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/messages$")
 
@@ -366,6 +330,7 @@ class RoomTriggerBackfill(RestServlet):
         defer.returnValue((200, res))
 
 
+# TODO: Needs unit testing
 class RoomMembershipRestServlet(RestServlet):
 
     def register(self, http_server):
@@ -402,7 +367,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 +387,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 +398,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,15 +410,20 @@ def register_txn_path(servlet, regex_string, http_server):
         client_path_pattern(regex_string + "/(?P<txn_id>[^/]*)$"),
         servlet.on_PUT
     )
+    if with_get:
+        http_server.register_path(
+        "GET",
+        client_path_pattern(regex_string + "/(?P<txn_id>[^/]*)$"),
+        servlet.on_GET
+    )
 
 
 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)
     RoomMessageListRestServlet(hs).register(http_server)
     JoinRoomAliasServlet(hs).register(http_server)
     RoomTriggerBackfill(hs).register(http_server)
     RoomMembershipRestServlet(hs).register(http_server)
+    RoomSendEventRestServlet(hs).register(http_server)
diff --git a/synapse/server.py b/synapse/server.py
index b825917748..9de88b6642 100644
--- a/synapse/server.py
+++ b/synapse/server.py
@@ -27,7 +27,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
@@ -115,6 +115,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
@@ -126,6 +129,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)
diff --git a/synapse/storage/__init__.py b/synapse/storage/__init__.py
index 130387184f..08505f05ff 100644
--- a/synapse/storage/__init__.py
+++ b/synapse/storage/__init__.py
@@ -88,7 +88,6 @@ class DataStore(RoomMemberStore, RoomStore,
             [
                 "event_id",
                 "type",
-                "sender",
                 "room_id",
                 "content",
                 "unrecognized_keys"
diff --git a/synapse/storage/feedback.py b/synapse/storage/feedback.py
index 336192ad01..bac3dea955 100644
--- a/synapse/storage/feedback.py
+++ b/synapse/storage/feedback.py
@@ -23,9 +23,9 @@ class FeedbackStore(SQLBaseStore):
     def _store_feedback_txn(self, txn, event):
         self._simple_insert_txn(txn, "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,
         })