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 ecb1e346d9..a01dab1b8e 100644
--- a/synapse/rest/room.py
+++ b/synapse/rest/room.py
@@ -19,7 +19,7 @@ from twisted.internet import defer
from base import RestServlet, client_path_pattern
from synapse.api.errors import SynapseError, Codes
from synapse.streams.config import PaginationConfig
-from synapse.api.events.room import RoomMemberEvent
+from synapse.api.events.room import RoomMemberEvent, RoomRedactionEvent
from synapse.api.constants import Membership
import json
@@ -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
@@ -430,6 +431,41 @@ class RoomMembershipRestServlet(RestServlet):
self.txns.store_client_transaction(request, txn_id, response)
defer.returnValue(response)
+class RoomRedactEventRestServlet(RestServlet):
+ def register(self, http_server):
+ PATTERN = ("/rooms/(?P<room_id>[^/]*)/redact/(?P<event_id>[^/]*)")
+ register_txn_path(self, PATTERN, http_server)
+
+ @defer.inlineCallbacks
+ def on_POST(self, request, room_id, event_id):
+ user = yield self.auth.get_user_by_req(request)
+ content = _parse_json(request)
+
+ event = self.event_factory.create_event(
+ etype=RoomRedactionEvent.TYPE,
+ room_id=urllib.unquote(room_id),
+ user_id=user.to_string(),
+ content=content,
+ redacts=event_id,
+ )
+
+ msg_handler = self.handlers.message_handler
+ yield msg_handler.send_message(event)
+
+ defer.returnValue((200, {"event_id": event.event_id}))
+
+ @defer.inlineCallbacks
+ def on_PUT(self, request, room_id, event_id, 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_id)
+
+ self.txns.store_client_transaction(request, txn_id, response)
+ defer.returnValue(response)
+
def _parse_json(request):
try:
@@ -485,3 +521,4 @@ def register_servlets(hs, http_server):
PublicRoomListRestServlet(hs).register(http_server)
RoomStateRestServlet(hs).register(http_server)
RoomInitialSyncRestServlet(hs).register(http_server)
+ RoomRedactEventRestServlet(hs).register(http_server)
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)
|