summary refs log tree commit diff
path: root/synapse
diff options
context:
space:
mode:
Diffstat (limited to 'synapse')
-rw-r--r--synapse/__init__.py2
-rw-r--r--synapse/api/auth.py4
-rw-r--r--synapse/config/captcha.py7
-rw-r--r--synapse/config/homeserver.py3
-rw-r--r--synapse/config/voip.py41
-rw-r--r--synapse/handlers/federation.py10
-rw-r--r--synapse/handlers/message.py16
-rw-r--r--synapse/handlers/room.py11
-rw-r--r--synapse/rest/__init__.py3
-rw-r--r--synapse/rest/register.py53
-rw-r--r--synapse/rest/room.py13
-rw-r--r--synapse/rest/voip.py60
-rw-r--r--synapse/storage/__init__.py3
13 files changed, 196 insertions, 30 deletions
diff --git a/synapse/__init__.py b/synapse/__init__.py
index 0dbad9a0de..bba551b2c4 100644
--- a/synapse/__init__.py
+++ b/synapse/__init__.py
@@ -16,4 +16,4 @@
 """ This is a reference implementation of a synapse home server.
 """
 
-__version__ = "0.3.2"
+__version__ = "0.3.3"
diff --git a/synapse/api/auth.py b/synapse/api/auth.py
index 9c65d47fb4..9bfd25c86e 100644
--- a/synapse/api/auth.py
+++ b/synapse/api/auth.py
@@ -400,11 +400,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/config/captcha.py b/synapse/config/captcha.py
index 8ebcfc3623..4ed9070b9e 100644
--- a/synapse/config/captcha.py
+++ b/synapse/config/captcha.py
@@ -24,6 +24,7 @@ class CaptchaConfig(Config):
         self.captcha_ip_origin_is_x_forwarded = (
             args.captcha_ip_origin_is_x_forwarded
         )
+        self.captcha_bypass_secret = args.captcha_bypass_secret
 
     @classmethod
     def add_arguments(cls, parser):
@@ -43,4 +44,8 @@ class CaptchaConfig(Config):
             "--captcha_ip_origin_is_x_forwarded", type=bool, default=False,
             help="When checking captchas, use the X-Forwarded-For (XFF) header"
             + " as the client IP and not the actual client IP."
-        )
\ No newline at end of file
+        )
+        group.add_argument(
+            "--captcha_bypass_secret", type=str,
+            help="A secret key used to bypass the captcha test entirely."
+        )
diff --git a/synapse/config/homeserver.py b/synapse/config/homeserver.py
index 4b810a2302..5a11fd6c76 100644
--- a/synapse/config/homeserver.py
+++ b/synapse/config/homeserver.py
@@ -21,11 +21,12 @@ from .ratelimiting import RatelimitConfig
 from .repository import ContentRepositoryConfig
 from .captcha import CaptchaConfig
 from .email import EmailConfig
+from .voip import VoipConfig
 
 
 class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
                        RatelimitConfig, ContentRepositoryConfig, CaptchaConfig,
-                       EmailConfig):
+                       EmailConfig, VoipConfig):
     pass
 
 
diff --git a/synapse/config/voip.py b/synapse/config/voip.py
new file mode 100644
index 0000000000..c5131d9bcd
--- /dev/null
+++ b/synapse/config/voip.py
@@ -0,0 +1,41 @@
+# 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 ._base import Config
+
+
+class VoipConfig(Config):
+
+    def __init__(self, args):
+        super(VoipConfig, self).__init__(args)
+        self.turn_uris = args.turn_uris.split(",") if args.turn_uris else None
+        self.turn_shared_secret = args.turn_shared_secret
+        self.turn_user_lifetime = args.turn_user_lifetime
+
+    @classmethod
+    def add_arguments(cls, parser):
+        super(VoipConfig, cls).add_arguments(parser)
+        group = parser.add_argument_group("voip")
+        group.add_argument(
+            "--turn-uris", type=str, default=None,
+            help="The public URIs of the TURN server to give to clients"
+        )
+        group.add_argument(
+            "--turn-shared-secret", type=str, default=None,
+            help="The shared secret used to compute passwords for the TURN server"
+        )
+        group.add_argument(
+            "--turn-user-lifetime", type=int, default=(1000 * 60 * 60),
+            help="How long generated TURN credentials last, in ms"
+        )
diff --git a/synapse/handlers/federation.py b/synapse/handlers/federation.py
index 001c6c110c..f52591d2a3 100644
--- a/synapse/handlers/federation.py
+++ b/synapse/handlers/federation.py
@@ -169,7 +169,15 @@ class FederationHandler(BaseHandler):
                     )
 
             if not backfilled:
-                yield self.notifier.on_new_room_event(event)
+                extra_users = []
+                if event.type == RoomMemberEvent.TYPE:
+                    target_user_id = event.state_key
+                    target_user = self.hs.parse_userid(target_user_id)
+                    extra_users.append(target_user)
+
+                yield self.notifier.on_new_room_event(
+                    event, extra_users=extra_users
+                )
 
         if event.type == RoomMemberEvent.TYPE:
             if event.membership == Membership.JOIN:
diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py
index 14fae689f2..317ef2c80c 100644
--- a/synapse/handlers/message.py
+++ b/synapse/handlers/message.py
@@ -233,6 +233,22 @@ class MessageHandler(BaseHandler):
         yield self._on_new_room_event(event, snapshot)
 
     @defer.inlineCallbacks
+    def get_state_events(self, user_id, room_id):
+        """Retrieve all state events for a given room.
+
+        Args:
+            user_id(str): The user requesting state events.
+            room_id(str): The room ID to get all state events from.
+        Returns:
+            A list of dicts representing state events. [{}, {}, {}]
+        """
+        yield self.auth.check_joined_room(room_id, user_id)
+
+        # TODO: This is duplicating logic from snapshot_all_rooms
+        current_state = yield self.store.get_current_state(room_id)
+        defer.returnValue([self.hs.serialize_event(c) for c in current_state])
+
+    @defer.inlineCallbacks
     def snapshot_all_rooms(self, user_id=None, pagin_config=None,
                            feedback=False):
         """Retrieve a snapshot of all rooms the user is invited or has joined.
diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py
index b7a9d77ca2..c0f9a7c807 100644
--- a/synapse/handlers/room.py
+++ b/synapse/handlers/room.py
@@ -146,17 +146,6 @@ class RoomCreationHandler(BaseHandler):
             )
 
             yield handle_event(name_event)
-        elif room_alias:
-            name = room_alias.to_string()
-            name_event = self.event_factory.create_event(
-                etype=RoomNameEvent.TYPE,
-                room_id=room_id,
-                user_id=user_id,
-                required_power_level=50,
-                content={"name": name},
-            )
-
-            yield handle_event(name_event)
 
         if "topic" in config:
             topic = config["topic"]
diff --git a/synapse/rest/__init__.py b/synapse/rest/__init__.py
index ed785cfbd5..3b9aa59733 100644
--- a/synapse/rest/__init__.py
+++ b/synapse/rest/__init__.py
@@ -15,7 +15,7 @@
 
 
 from . import (
-    room, events, register, login, profile, presence, initial_sync, directory
+    room, events, register, login, profile, presence, initial_sync, directory, voip
 )
 
 
@@ -42,3 +42,4 @@ class RestServletFactory(object):
         presence.register_servlets(hs, client_resource)
         initial_sync.register_servlets(hs, client_resource)
         directory.register_servlets(hs, client_resource)
+        voip.register_servlets(hs, client_resource)
diff --git a/synapse/rest/register.py b/synapse/rest/register.py
index af528a44f6..4935e323d9 100644
--- a/synapse/rest/register.py
+++ b/synapse/rest/register.py
@@ -21,6 +21,8 @@ from synapse.api.constants import LoginType
 from base import RestServlet, client_path_pattern
 import synapse.util.stringutils as stringutils
 
+from hashlib import sha1
+import hmac
 import json
 import logging
 import urllib
@@ -28,6 +30,16 @@ import urllib
 logger = logging.getLogger(__name__)
 
 
+# We ought to be using hmac.compare_digest() but on older pythons it doesn't
+# exist. It's a _really minor_ security flaw to use plain string comparison
+# because the timing attack is so obscured by all the other code here it's
+# unlikely to make much difference
+if hasattr(hmac, "compare_digest"):
+    compare_digest = hmac.compare_digest
+else:
+    compare_digest = lambda a, b: a == b
+
+
 class RegisterRestServlet(RestServlet):
     """Handles registration with the home server.
 
@@ -142,6 +154,38 @@ class RegisterRestServlet(RestServlet):
         if not self.hs.config.enable_registration_captcha:
             raise SynapseError(400, "Captcha not required.")
 
+        yield self._check_recaptcha(request, register_json, session)
+
+        session[LoginType.RECAPTCHA] = True  # mark captcha as done
+        self._save_session(session)
+        defer.returnValue({
+            "next": [LoginType.PASSWORD, LoginType.EMAIL_IDENTITY]
+        })
+
+    @defer.inlineCallbacks
+    def _check_recaptcha(self, request, register_json, session):
+        if ("captcha_bypass_hmac" in register_json and
+                self.hs.config.captcha_bypass_secret):
+            if "user" not in register_json:
+                raise SynapseError(400, "Captcha bypass needs 'user'")
+
+            want = hmac.new(
+                key=self.hs.config.captcha_bypass_secret,
+                msg=register_json["user"],
+                digestmod=sha1,
+            ).hexdigest()
+
+            # str() because otherwise hmac complains that 'unicode' does not
+            # have the buffer interface
+            got = str(register_json["captcha_bypass_hmac"])
+
+            if compare_digest(want, got):
+                session["user"] = register_json["user"]
+                defer.returnValue(None)
+            else:
+                raise SynapseError(400, "Captcha bypass HMAC incorrect",
+                    errcode=Codes.CAPTCHA_NEEDED)
+
         challenge = None
         user_response = None
         try:
@@ -166,11 +210,6 @@ class RegisterRestServlet(RestServlet):
             challenge,
             user_response
         )
-        session[LoginType.RECAPTCHA] = True  # mark captcha as done
-        self._save_session(session)
-        defer.returnValue({
-            "next": [LoginType.PASSWORD, LoginType.EMAIL_IDENTITY]
-        })
 
     @defer.inlineCallbacks
     def _do_email_identity(self, request, register_json, session):
@@ -195,6 +234,10 @@ class RegisterRestServlet(RestServlet):
             # captcha should've been done by this stage!
             raise SynapseError(400, "Captcha is required.")
 
+        if ("user" in session and "user" in register_json and
+                session["user"] != register_json["user"]):
+            raise SynapseError(400, "Cannot change user ID during registration")
+
         password = register_json["password"].encode("utf-8")
         desired_user_id = (register_json["user"].encode("utf-8") if "user"
                           in register_json else None)
diff --git a/synapse/rest/room.py b/synapse/rest/room.py
index 399084ae2a..a01dab1b8e 100644
--- a/synapse/rest/room.py
+++ b/synapse/rest/room.py
@@ -329,12 +329,13 @@ class RoomStateRestServlet(RestServlet):
     @defer.inlineCallbacks
     def on_GET(self, request, room_id):
         user = yield self.auth.get_user_by_req(request)
-        # TODO: Get all the current state for this room and return in the same
-        # format as initial sync, that is:
-        # [
-        #   { state event }, { state event }
-        # ]
-        defer.returnValue((200, []))
+        handler = self.handlers.message_handler
+        # Get all the current state for this room
+        events = yield handler.get_state_events(
+            room_id=urllib.unquote(room_id),
+            user_id=user.to_string(),
+        )
+        defer.returnValue((200, events))
 
 
 # TODO: Needs unit testing
diff --git a/synapse/rest/voip.py b/synapse/rest/voip.py
new file mode 100644
index 0000000000..2e4627606f
--- /dev/null
+++ b/synapse/rest/voip.py
@@ -0,0 +1,60 @@
+# -*- 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 twisted.internet import defer
+
+from base import RestServlet, client_path_pattern
+
+
+import hmac
+import hashlib
+import base64
+
+
+class VoipRestServlet(RestServlet):
+    PATTERN = client_path_pattern("/voip/turnServer$")
+
+    @defer.inlineCallbacks
+    def on_GET(self, request):
+        auth_user = yield self.auth.get_user_by_req(request)
+
+        turnUris = self.hs.config.turn_uris
+        turnSecret = self.hs.config.turn_shared_secret
+        userLifetime = self.hs.config.turn_user_lifetime
+        if not turnUris or not turnSecret or not userLifetime:
+            defer.returnValue( (200, {}) )
+
+        expiry = self.hs.get_clock().time_msec() + userLifetime
+        username = "%d:%s" % (expiry, auth_user.to_string())
+         
+        mac = hmac.new(turnSecret, msg=username, digestmod=hashlib.sha1)
+        # We need to use standard base64 encoding here, *not* syutil's encode_base64
+        # because we need to add the standard padding to get the same result as the
+        # TURN server.
+        password = base64.b64encode(mac.digest())
+
+        defer.returnValue( (200, {
+            'username': username,
+            'password': password,
+            'ttl': userLifetime / 1000,
+            'uris': turnUris,
+        }) )
+
+    def on_OPTIONS(self, request):
+        return (200, {})
+
+
+def register_servlets(hs, http_server):
+    VoipRestServlet(hs).register(http_server)
diff --git a/synapse/storage/__init__.py b/synapse/storage/__init__.py
index 38f5ec0ac5..15919eb580 100644
--- a/synapse/storage/__init__.py
+++ b/synapse/storage/__init__.py
@@ -221,7 +221,8 @@ class DataStore(RoomMemberStore, RoomStore,
             )
             raise _RollbackButIsFineException("_persist_event")
 
-        if is_new_state and hasattr(event, "state_key"):
+        is_state = hasattr(event, "state_key") and event.state_key is not None
+        if is_new_state and is_state:
             vals = {
                 "event_id": event.event_id,
                 "room_id": event.room_id,