summary refs log tree commit diff
path: root/synapse/api
diff options
context:
space:
mode:
Diffstat (limited to 'synapse/api')
-rw-r--r--synapse/api/auth.py80
-rw-r--r--synapse/api/events/__init__.py7
-rw-r--r--synapse/api/events/factory.py4
-rw-r--r--synapse/api/events/room.py9
-rw-r--r--synapse/api/events/utils.py64
5 files changed, 150 insertions, 14 deletions
diff --git a/synapse/api/auth.py b/synapse/api/auth.py
index 8f32191b57..e1b1823cd7 100644
--- a/synapse/api/auth.py
+++ b/synapse/api/auth.py
@@ -19,7 +19,9 @@ from twisted.internet import defer
 
 from synapse.api.constants import Membership, JoinRules
 from synapse.api.errors import AuthError, StoreError, Codes, SynapseError
-from synapse.api.events.room import RoomMemberEvent, RoomPowerLevelsEvent
+from synapse.api.events.room import (
+    RoomMemberEvent, RoomPowerLevelsEvent, RoomRedactionEvent,
+)
 from synapse.util.logutils import log_function
 
 import logging
@@ -70,6 +72,9 @@ class Auth(object):
                 if event.type == RoomPowerLevelsEvent.TYPE:
                     yield self._check_power_levels(event)
 
+                if event.type == RoomRedactionEvent.TYPE:
+                    yield self._check_redaction(event)
+
                 defer.returnValue(True)
             else:
                 raise AuthError(500, "Unknown event: %s" % event)
@@ -170,7 +175,7 @@ class Auth(object):
                     event.room_id,
                     event.user_id,
                 )
-                _, kick_level = yield self.store.get_ops_levels(event.room_id)
+                _, kick_level, _ = yield self.store.get_ops_levels(event.room_id)
 
                 if kick_level:
                     kick_level = int(kick_level)
@@ -187,7 +192,7 @@ class Auth(object):
                 event.user_id,
             )
 
-            ban_level, _ = yield self.store.get_ops_levels(event.room_id)
+            ban_level, _, _  = yield self.store.get_ops_levels(event.room_id)
 
             if ban_level:
                 ban_level = int(ban_level)
@@ -201,6 +206,7 @@ class Auth(object):
 
         defer.returnValue(True)
 
+    @defer.inlineCallbacks
     def get_user_by_req(self, request):
         """ Get a registered user's ID.
 
@@ -213,7 +219,25 @@ class Auth(object):
         """
         # Can optionally look elsewhere in the request (e.g. headers)
         try:
-            return self.get_user_by_token(request.args["access_token"][0])
+            access_token = request.args["access_token"][0]
+            user_info = yield self.get_user_by_token(access_token)
+            user = user_info["user"]
+
+            ip_addr = self.hs.get_ip_from_request(request)
+            user_agent = request.requestHeaders.getRawHeaders(
+                "User-Agent",
+                default=[""]
+            )[0]
+            if user and access_token and ip_addr:
+                self.store.insert_client_ip(
+                    user=user,
+                    access_token=access_token,
+                    device_id=user_info["device_id"],
+                    ip=ip_addr,
+                    user_agent=user_agent
+                )
+
+            defer.returnValue(user)
         except KeyError:
             raise AuthError(403, "Missing access token.")
 
@@ -222,21 +246,32 @@ class Auth(object):
         """ Get a registered user's ID.
 
         Args:
-            token (str)- The access token to get the user by.
+            token (str): The access token to get the user by.
         Returns:
-            UserID : User ID object of the user who has that access token.
+            dict : dict that includes the user, device_id, and whether the
+                user is a server admin.
         Raises:
             AuthError if no user by that token exists or the token is invalid.
         """
         try:
-            user_id = yield self.store.get_user_by_token(token=token)
-            if not user_id:
+            ret = yield self.store.get_user_by_token(token=token)
+            if not ret:
                 raise StoreError()
-            defer.returnValue(self.hs.parse_userid(user_id))
+
+            user_info = {
+                "admin": bool(ret.get("admin", False)),
+                "device_id": ret.get("device_id"),
+                "user": self.hs.parse_userid(ret.get("name")),
+            }
+
+            defer.returnValue(user_info)
         except StoreError:
             raise AuthError(403, "Unrecognised access token.",
                             errcode=Codes.UNKNOWN_TOKEN)
 
+    def is_server_admin(self, user):
+        return self.store.is_server_admin(user)
+
     @defer.inlineCallbacks
     @log_function
     def _can_send_event(self, event):
@@ -322,6 +357,29 @@ class Auth(object):
                 )
 
     @defer.inlineCallbacks
+    def _check_redaction(self, event):
+        user_level = yield self.store.get_power_level(
+            event.room_id,
+            event.user_id,
+        )
+
+        if user_level:
+            user_level = int(user_level)
+        else:
+            user_level = 0
+
+        _, _, redact_level  = yield self.store.get_ops_levels(event.room_id)
+
+        if not redact_level:
+            redact_level = 50
+
+        if user_level < redact_level:
+            raise AuthError(
+                403,
+                "You don't have permission to redact events"
+            )
+
+    @defer.inlineCallbacks
     def _check_power_levels(self, event):
         for k, v in event.content.items():
             if k == "default":
@@ -372,11 +430,11 @@ class Auth(object):
         }
 
         removed = set(old_people.keys()) - set(new_people.keys())
-        added = set(old_people.keys()) - set(new_people.keys())
+        added = set(new_people.keys()) - set(old_people.keys())
         same = set(old_people.keys()) & set(new_people.keys())
 
         for r in removed:
-            if int(old_list.content[r]) > user_level:
+            if int(old_list[r]) > user_level:
                 raise AuthError(
                     403,
                     "You don't have permission to remove user: %s" % (r, )
diff --git a/synapse/api/events/__init__.py b/synapse/api/events/__init__.py
index 0cee196851..f66fea2904 100644
--- a/synapse/api/events/__init__.py
+++ b/synapse/api/events/__init__.py
@@ -22,7 +22,8 @@ def serialize_event(hs, e):
     if not isinstance(e, SynapseEvent):
         return e
 
-    d = e.get_dict()
+    # Should this strip out None's?
+    d = {k: v for k, v in e.get_dict().items()}
     if "age_ts" in d:
         d["age"] = int(hs.get_clock().time_msec()) - d["age_ts"]
         del d["age_ts"]
@@ -58,17 +59,19 @@ class SynapseEvent(JsonEncodedObject):
         "required_power_level",
         "age_ts",
         "prev_content",
+        "prev_state",
+        "redacted_because",
     ]
 
     internal_keys = [
         "is_state",
         "prev_events",
-        "prev_state",
         "depth",
         "destinations",
         "origin",
         "outlier",
         "power_level",
+        "redacted",
     ]
 
     required_keys = [
diff --git a/synapse/api/events/factory.py b/synapse/api/events/factory.py
index d3d96d73eb..0d94850cec 100644
--- a/synapse/api/events/factory.py
+++ b/synapse/api/events/factory.py
@@ -17,7 +17,8 @@ from synapse.api.events.room import (
     RoomTopicEvent, MessageEvent, RoomMemberEvent, FeedbackEvent,
     InviteJoinEvent, RoomConfigEvent, RoomNameEvent, GenericEvent,
     RoomPowerLevelsEvent, RoomJoinRulesEvent, RoomOpsPowerLevelsEvent,
-    RoomCreateEvent, RoomAddStateLevelEvent, RoomSendEventLevelEvent
+    RoomCreateEvent, RoomAddStateLevelEvent, RoomSendEventLevelEvent,
+    RoomRedactionEvent,
 )
 
 from synapse.util.stringutils import random_string
@@ -39,6 +40,7 @@ class EventFactory(object):
         RoomAddStateLevelEvent,
         RoomSendEventLevelEvent,
         RoomOpsPowerLevelsEvent,
+        RoomRedactionEvent,
     ]
 
     def __init__(self, hs):
diff --git a/synapse/api/events/room.py b/synapse/api/events/room.py
index 3a4dbc58ce..cd936074fc 100644
--- a/synapse/api/events/room.py
+++ b/synapse/api/events/room.py
@@ -180,3 +180,12 @@ class RoomAliasesEvent(SynapseStateEvent):
 
     def get_content_template(self):
         return {}
+
+
+class RoomRedactionEvent(SynapseEvent):
+    TYPE = "m.room.redaction"
+
+    valid_keys = SynapseEvent.valid_keys + ["redacts"]
+
+    def get_content_template(self):
+        return {}
diff --git a/synapse/api/events/utils.py b/synapse/api/events/utils.py
new file mode 100644
index 0000000000..c3a32be8c1
--- /dev/null
+++ b/synapse/api/events/utils.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+# Copyright 2014 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.
+
+from .room import (
+    RoomMemberEvent, RoomJoinRulesEvent, RoomPowerLevelsEvent,
+    RoomAddStateLevelEvent, RoomSendEventLevelEvent, RoomOpsPowerLevelsEvent,
+    RoomAliasesEvent, RoomCreateEvent,
+)
+
+def prune_event(event):
+    """ Prunes the given event of all keys we don't know about or think could
+    potentially be dodgy.
+
+    This is used when we "redact" an event. We want to remove all fields that
+    the user has specified, but we do want to keep necessary information like
+    type, state_key etc.
+    """
+
+    # Remove all extraneous fields.
+    event.unrecognized_keys = {}
+
+    new_content = {}
+
+    def add_fields(*fields):
+        for field in fields:
+            if field in event.content:
+                new_content[field] = event.content[field]
+
+    if event.type == RoomMemberEvent.TYPE:
+        add_fields("membership")
+    elif event.type == RoomCreateEvent.TYPE:
+        add_fields("creator")
+    elif event.type == RoomJoinRulesEvent.TYPE:
+        add_fields("join_rule")
+    elif event.type == RoomPowerLevelsEvent.TYPE:
+        # TODO: Actually check these are valid user_ids etc.
+        add_fields("default")
+        for k, v in event.content.items():
+            if k.startswith("@") and isinstance(v, (int, long)):
+                new_content[k] = v
+    elif event.type == RoomAddStateLevelEvent.TYPE:
+        add_fields("level")
+    elif event.type == RoomSendEventLevelEvent.TYPE:
+        add_fields("level")
+    elif event.type == RoomOpsPowerLevelsEvent.TYPE:
+        add_fields("kick_level", "ban_level", "redact_level")
+    elif event.type == RoomAliasesEvent.TYPE:
+        add_fields("aliases")
+
+    event.content = new_content
+
+    return event