From 231afe464aa9d5313de29ff569a1e2fea842fcb5 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Mon, 22 Sep 2014 13:42:52 +0100 Subject: Add a deletions table --- synapse/storage/schema/delta/v4.sql | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 synapse/storage/schema/delta/v4.sql (limited to 'synapse') diff --git a/synapse/storage/schema/delta/v4.sql b/synapse/storage/schema/delta/v4.sql new file mode 100644 index 0000000000..1652ef2921 --- /dev/null +++ b/synapse/storage/schema/delta/v4.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS deletions ( + event_id TEXT NOT NULL, + deletes TEXT NOT NULL, + CONSTRAINT ev_uniq UNIQUE (event_id) +); -- cgit 1.5.1 From 3a8a94448af334e57c5cfa3583b2c20739aeb613 Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Tue, 23 Sep 2014 14:29:08 +0100 Subject: Allow a (hidden undocumented) key to m.login.recaptcha to specify a shared secret to allow bots to bypass the ReCAPTCHA test (SYN-60) --- synapse/config/captcha.py | 7 ++++++- synapse/rest/register.py | 23 ++++++++++++++++++----- 2 files changed, 24 insertions(+), 6 deletions(-) (limited to 'synapse') 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/rest/register.py b/synapse/rest/register.py index af528a44f6..f1354e4b71 100644 --- a/synapse/rest/register.py +++ b/synapse/rest/register.py @@ -142,6 +142,24 @@ 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[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): + if "captcha_bypass_secret" in register_json: + if (register_json["captcha_bypass_secret"] == + self.hs.config.captcha_bypass_secret): + defer.returnValue(None) + else: + raise SynapseError(400, "Captcha bypass secret incorrect", + errcode=Codes.CAPTCHA_NEEDED) + challenge = None user_response = None try: @@ -166,11 +184,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): -- cgit 1.5.1 From 5f16439752fa6ff9b452cac86fbbb07a12ae44f7 Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Tue, 23 Sep 2014 15:16:47 +0100 Subject: Make sure the config actually /has/ a captcha_bypass_secret set before trying to compare it --- synapse/rest/register.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'synapse') diff --git a/synapse/rest/register.py b/synapse/rest/register.py index f1354e4b71..3b07a127a6 100644 --- a/synapse/rest/register.py +++ b/synapse/rest/register.py @@ -153,8 +153,9 @@ class RegisterRestServlet(RestServlet): @defer.inlineCallbacks def _check_recaptcha(self, request, register_json): if "captcha_bypass_secret" in register_json: - if (register_json["captcha_bypass_secret"] == - self.hs.config.captcha_bypass_secret): + if (self.hs.config.captcha_bypass_secret is not None and + register_json["captcha_bypass_secret"] == + self.hs.config.captcha_bypass_secret): defer.returnValue(None) else: raise SynapseError(400, "Captcha bypass secret incorrect", -- cgit 1.5.1 From 537c7e1137684c97676bbb74d59cd93c5bf1aad1 Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Tue, 23 Sep 2014 15:18:59 +0100 Subject: Config values are almost never 'None', but they might be empty string. Detect their presence by truth --- synapse/rest/register.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'synapse') diff --git a/synapse/rest/register.py b/synapse/rest/register.py index 3b07a127a6..66cef26ada 100644 --- a/synapse/rest/register.py +++ b/synapse/rest/register.py @@ -153,7 +153,7 @@ class RegisterRestServlet(RestServlet): @defer.inlineCallbacks def _check_recaptcha(self, request, register_json): if "captcha_bypass_secret" in register_json: - if (self.hs.config.captcha_bypass_secret is not None and + if (self.hs.config.captcha_bypass_secret and register_json["captcha_bypass_secret"] == self.hs.config.captcha_bypass_secret): defer.returnValue(None) -- cgit 1.5.1 From 78af6bbb981c41e5509c99454deb7205c31bf964 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Tue, 23 Sep 2014 15:28:32 +0100 Subject: Add m.room.deletion. If an event is deleted it will be returned to clients 'pruned', i.e. all client specified keys will be removed. --- synapse/api/events/__init__.py | 6 ++++-- synapse/api/events/factory.py | 4 +++- synapse/api/events/room.py | 9 +++++++++ synapse/rest/room.py | 38 ++++++++++++++++++++++++++++++++++++- synapse/storage/__init__.py | 29 ++++++++++++++++++++++++---- synapse/storage/_base.py | 30 ++++++++++++++++++++++++++--- synapse/storage/roommember.py | 13 ++++++++++--- synapse/storage/schema/delta/v4.sql | 6 ++++-- synapse/storage/stream.py | 30 ++++++++++++++++++++++++----- 9 files changed, 144 insertions(+), 21 deletions(-) (limited to 'synapse') diff --git a/synapse/api/events/__init__.py b/synapse/api/events/__init__.py index 0cee196851..910c990b33 100644 --- a/synapse/api/events/__init__.py +++ b/synapse/api/events/__init__.py @@ -22,7 +22,7 @@ def serialize_event(hs, e): if not isinstance(e, SynapseEvent): return e - d = e.get_dict() + d = {k: v for k, v in e.get_dict().items() if v is not None or v is not False} if "age_ts" in d: d["age"] = int(hs.get_clock().time_msec()) - d["age_ts"] del d["age_ts"] @@ -58,17 +58,19 @@ class SynapseEvent(JsonEncodedObject): "required_power_level", "age_ts", "prev_content", + "prev_state", + "pruned", ] internal_keys = [ "is_state", "prev_events", - "prev_state", "depth", "destinations", "origin", "outlier", "power_level", + "deleted", ] required_keys = [ diff --git a/synapse/api/events/factory.py b/synapse/api/events/factory.py index d3d96d73eb..c65ea8372b 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, + RoomDeletionEvent, ) from synapse.util.stringutils import random_string @@ -39,6 +40,7 @@ class EventFactory(object): RoomAddStateLevelEvent, RoomSendEventLevelEvent, RoomOpsPowerLevelsEvent, + RoomDeletionEvent, ] def __init__(self, hs): diff --git a/synapse/api/events/room.py b/synapse/api/events/room.py index 3a4dbc58ce..9861395556 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 RoomDeletionEvent(SynapseEvent): + TYPE = "m.room.deletion" + + valid_keys = SynapseEvent.valid_keys + ["deletes"] + + def get_content_template(self): + return {} diff --git a/synapse/rest/room.py b/synapse/rest/room.py index ecb1e346d9..85a1d2eae3 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, RoomDeletionEvent from synapse.api.constants import Membership import json @@ -430,6 +430,41 @@ class RoomMembershipRestServlet(RestServlet): self.txns.store_client_transaction(request, txn_id, response) defer.returnValue(response) +class RoomDeleteEventRestServlet(RestServlet): + def register(self, http_server): + PATTERN = ("/rooms/(?P[^/]*)/delete/(?P[^/]*)") + 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=RoomDeletionEvent.TYPE, + room_id=urllib.unquote(room_id), + user_id=user.to_string(), + content=content, + deletes=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 +520,4 @@ def register_servlets(hs, http_server): PublicRoomListRestServlet(hs).register(http_server) RoomStateRestServlet(hs).register(http_server) RoomInitialSyncRestServlet(hs).register(http_server) + RoomDeleteEventRestServlet(hs).register(http_server) diff --git a/synapse/storage/__init__.py b/synapse/storage/__init__.py index 66658f6721..672ed6971e 100644 --- a/synapse/storage/__init__.py +++ b/synapse/storage/__init__.py @@ -24,6 +24,7 @@ from synapse.api.events.room import ( RoomAddStateLevelEvent, RoomSendEventLevelEvent, RoomOpsPowerLevelsEvent, + RoomDeletionEvent, ) from synapse.util.logutils import log_function @@ -61,7 +62,7 @@ SCHEMAS = [ # Remember to update this number every time an incompatible change is made to # database schema files, so the users will be informed on server restarts. -SCHEMA_VERSION = 3 +SCHEMA_VERSION = 4 class _RollbackButIsFineException(Exception): @@ -182,6 +183,8 @@ class DataStore(RoomMemberStore, RoomStore, self._store_send_event_level(txn, event) elif event.type == RoomOpsPowerLevelsEvent.TYPE: self._store_ops_level(txn, event) + elif event.type == RoomDeletionEvent.TYPE: + self._store_deletion(txn, event) vals = { "topological_ordering": event.depth, @@ -203,7 +206,7 @@ class DataStore(RoomMemberStore, RoomStore, unrec = { k: v for k, v in event.get_full_dict().items() - if k not in vals.keys() + if k not in vals.keys() and k is not "deleted" } vals["unrecognized_keys"] = json.dumps(unrec) @@ -241,14 +244,32 @@ class DataStore(RoomMemberStore, RoomStore, } ) + def _store_deletion(self, txn, event): + event_id = event.event_id + deletes = event.deletes + + # We check if this new delete deletes an old delete or has been + # deleted by a previous delete that we received out of order. + sql = "SELECT * FROM deletions WHERE event_id = ? OR deletes = ?" + txn.execute(sql, (deletes, event_id)) + + if txn.fetchall(): + sql = "DELETE FROM deletions WHERE event_id = ? OR deletes = ?" + txn.execute(sql, (deletes, event_id, )) + else: + sql = "INSERT INTO deletions (event_id, deletes) VALUES (?,?)" + txn.execute(sql, (event_id, deletes)) + @defer.inlineCallbacks def get_current_state(self, room_id, event_type=None, state_key=""): sql = ( - "SELECT e.* FROM events as e " + "SELECT e.*, (%(deleted)s) AS deleted FROM events as e " "INNER JOIN current_state_events as c ON e.event_id = c.event_id " "INNER JOIN state_events as s ON e.event_id = s.event_id " "WHERE c.room_id = ? " - ) + ) % { + "deleted": "e.event_id IN (SELECT deletes FROM deletions)", + } if event_type: sql += " AND s.type = ? AND s.state_key = ? " diff --git a/synapse/storage/_base.py b/synapse/storage/_base.py index 76ed7d06fb..3aa610c85c 100644 --- a/synapse/storage/_base.py +++ b/synapse/storage/_base.py @@ -17,6 +17,7 @@ import logging from twisted.internet import defer from synapse.api.errors import StoreError +from synapse.api.events.utils import prune_event from synapse.util.logutils import log_function import collections @@ -345,7 +346,7 @@ class SQLBaseStore(object): return self.runInteraction(func) def _parse_event_from_row(self, row_dict): - d = copy.deepcopy({k: v for k, v in row_dict.items() if v}) + d = copy.deepcopy({k: v for k, v in row_dict.items()}) d.pop("stream_ordering", None) d.pop("topological_ordering", None) @@ -373,8 +374,8 @@ class SQLBaseStore(object): sql = "SELECT * FROM events WHERE event_id = ?" for ev in events: - if hasattr(ev, "prev_state"): - # Load previous state_content. + if hasattr(ev, "prev_state"): + # Load previous state_content. # TODO: Should we be pulling this out above? cursor = txn.execute(sql, (ev.prev_state,)) prevs = self.cursor_to_dict(cursor) @@ -382,8 +383,31 @@ class SQLBaseStore(object): prev = self._parse_event_from_row(prevs[0]) ev.prev_content = prev.content + if not hasattr(ev, "deleted"): + logger.debug("Doesn't have deleted key: %s", ev) + ev.deleted = self._has_been_deleted_txn(txn, ev) + + if ev.deleted: + # Get the deletion event. + sql = "SELECT * FROM events WHERE event_id = ?" + txn.execute(sql, (ev.deleted,)) + + del_evs = self._parse_events_txn( + txn, self.cursor_to_dict(txn) + ) + + if del_evs: + prune_event(ev) + ev.pruned = del_evs[0] + return events + def _has_been_deleted_txn(self, txn, event): + sql = "SELECT * FROM deletions WHERE deletes = ?" + txn.execute(sql, (event.event_id,)) + return len(txn.fetchall()) > 0 + + class Table(object): """ A base class used to store information about a particular table. """ diff --git a/synapse/storage/roommember.py b/synapse/storage/roommember.py index 04b4067d03..97222da571 100644 --- a/synapse/storage/roommember.py +++ b/synapse/storage/roommember.py @@ -182,14 +182,21 @@ class RoomMemberStore(SQLBaseStore): ) def _get_members_query_txn(self, txn, where_clause, where_values): + del_sql = ( + "SELECT event_id FROM deletions WHERE deletes = e.event_id" + ) + sql = ( - "SELECT e.* FROM events as e " + "SELECT e.*, (%(deleted)s) AS deleted FROM events as e " "INNER JOIN room_memberships as m " "ON e.event_id = m.event_id " "INNER JOIN current_state_events as c " "ON m.event_id = c.event_id " - "WHERE %s " - ) % (where_clause,) + "WHERE %(where)s " + ) % { + "deleted": del_sql, + "where": where_clause, + } txn.execute(sql, where_values) rows = self.cursor_to_dict(txn) diff --git a/synapse/storage/schema/delta/v4.sql b/synapse/storage/schema/delta/v4.sql index 1652ef2921..2e2635317a 100644 --- a/synapse/storage/schema/delta/v4.sql +++ b/synapse/storage/schema/delta/v4.sql @@ -1,5 +1,7 @@ CREATE TABLE IF NOT EXISTS deletions ( event_id TEXT NOT NULL, - deletes TEXT NOT NULL, - CONSTRAINT ev_uniq UNIQUE (event_id) + deletes TEXT NOT NULL ); + +CREATE INDEX IF NOT EXISTS deletions_event_id ON deletions (event_id); +CREATE INDEX IF NOT EXISTS deletions_deletes ON deletions (deletes); diff --git a/synapse/storage/stream.py b/synapse/storage/stream.py index a76fecf24f..aaac0aae30 100644 --- a/synapse/storage/stream.py +++ b/synapse/storage/stream.py @@ -157,6 +157,10 @@ class StreamStore(SQLBaseStore): "WHERE m.user_id = ? " ) + del_sql = ( + "SELECT event_id FROM deletions WHERE deletes = e.event_id" + ) + if limit: limit = max(limit, MAX_STREAM_SIZE) else: @@ -171,13 +175,14 @@ class StreamStore(SQLBaseStore): return sql = ( - "SELECT * FROM events as e WHERE " + "SELECT *, (%(deleted)s) AS deleted FROM events AS e WHERE " "((room_id IN (%(current)s)) OR " "(event_id IN (%(invites)s))) " "AND e.stream_ordering > ? AND e.stream_ordering <= ? " "AND e.outlier = 0 " "ORDER BY stream_ordering ASC LIMIT %(limit)d " ) % { + "deleted": del_sql, "current": current_room_membership_sql, "invites": membership_sql, "limit": limit @@ -224,11 +229,20 @@ class StreamStore(SQLBaseStore): else: limit_str = "" + del_sql = ( + "SELECT event_id FROM deletions WHERE deletes = events.event_id" + ) + sql = ( - "SELECT * FROM events " + "SELECT *, (%(deleted)s) AS deleted FROM events " "WHERE outlier = 0 AND room_id = ? AND %(bounds)s " "ORDER BY topological_ordering %(order)s, stream_ordering %(order)s %(limit)s " - ) % {"bounds": bounds, "order": order, "limit": limit_str} + ) % { + "deleted": del_sql, + "bounds": bounds, + "order": order, + "limit": limit_str + } rows = yield self._execute_and_decode( sql, @@ -257,11 +271,17 @@ class StreamStore(SQLBaseStore): with_feedback=False): # TODO (erikj): Handle compressed feedback + del_sql = ( + "SELECT event_id FROM deletions WHERE deletes = events.event_id" + ) + sql = ( - "SELECT * FROM events " + "SELECT *, (%(deleted)s) AS deleted FROM events " "WHERE room_id = ? AND stream_ordering <= ? " "ORDER BY topological_ordering DESC, stream_ordering DESC LIMIT ? " - ) + ) % { + "deleted": del_sql, + } rows = yield self._execute_and_decode( sql, -- cgit 1.5.1 From b99f6eb904c9614aacd3532196be410341d0640b Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Tue, 23 Sep 2014 15:29:27 +0100 Subject: Make sure we don't persist the 'pruned' key --- synapse/storage/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'synapse') diff --git a/synapse/storage/__init__.py b/synapse/storage/__init__.py index 672ed6971e..46d420b632 100644 --- a/synapse/storage/__init__.py +++ b/synapse/storage/__init__.py @@ -206,7 +206,7 @@ class DataStore(RoomMemberStore, RoomStore, unrec = { k: v for k, v in event.get_full_dict().items() - if k not in vals.keys() and k is not "deleted" + if k not in vals.keys() and k not in ["deleted", "pruned"] } vals["unrecognized_keys"] = json.dumps(unrec) -- cgit 1.5.1 From 0c4ae63ad52a0bbe6a7a0cc519686b57f655e254 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 23 Sep 2014 15:35:58 +0100 Subject: Implemented /rooms/$roomid/state API. --- synapse/handlers/message.py | 16 ++++++++++++++++ synapse/rest/room.py | 13 +++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) (limited to 'synapse') 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 @@ -232,6 +232,22 @@ class MessageHandler(BaseHandler): # store message in db 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): diff --git a/synapse/rest/room.py b/synapse/rest/room.py index ecb1e346d9..cf2e7af2e4 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 -- cgit 1.5.1 From 932b376b4e3d1992268274f25e2343f6e81d93f8 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Tue, 23 Sep 2014 15:37:32 +0100 Subject: Add prune_event method --- synapse/api/events/utils.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 synapse/api/events/utils.py (limited to 'synapse') diff --git a/synapse/api/events/utils.py b/synapse/api/events/utils.py new file mode 100644 index 0000000000..dfefb2662a --- /dev/null +++ b/synapse/api/events/utils.py @@ -0,0 +1,39 @@ +# -*- 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 + +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 "delete" 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 = {} + + if event.type == RoomMemberEvent.TYPE: + new_content = { + "membership": event.content["membership"] + } + else: + new_content = {} + + event.content = new_content + + return event -- cgit 1.5.1 From c03176af59adfe0d60ffd8beb8bf262b4563d20b Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Tue, 23 Sep 2014 15:58:44 +0100 Subject: Send an HMAC(SHA1) protecting the User ID for the ReCAPTCHA bypass, rather than simply the secret itself, so it's useless if that HMAC leaks --- synapse/rest/register.py | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) (limited to 'synapse') diff --git a/synapse/rest/register.py b/synapse/rest/register.py index 66cef26ada..14d1ab018e 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 @@ -142,7 +144,7 @@ class RegisterRestServlet(RestServlet): if not self.hs.config.enable_registration_captcha: raise SynapseError(400, "Captcha not required.") - yield self._check_recaptcha(request, register_json) + yield self._check_recaptcha(request, register_json, session) session[LoginType.RECAPTCHA] = True # mark captcha as done self._save_session(session) @@ -151,14 +153,27 @@ class RegisterRestServlet(RestServlet): }) @defer.inlineCallbacks - def _check_recaptcha(self, request, register_json): - if "captcha_bypass_secret" in register_json: - if (self.hs.config.captcha_bypass_secret and - register_json["captcha_bypass_secret"] == - self.hs.config.captcha_bypass_secret): + 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 hmac.compare_digest(want, got): + session["user"] = register_json["user"] defer.returnValue(None) else: - raise SynapseError(400, "Captcha bypass secret incorrect", + raise SynapseError(400, "Captcha bypass HMAC incorrect", errcode=Codes.CAPTCHA_NEEDED) challenge = None @@ -209,6 +224,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) -- cgit 1.5.1 From 14ed6799d72c7807467456808aa08a6f376ebe14 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 23 Sep 2014 17:16:13 +0100 Subject: Add support for TURN servers as per the TURN REST API (http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00) --- synapse/config/homeserver.py | 3 ++- synapse/config/voip.py | 41 ++++++++++++++++++++++++++++++ synapse/rest/__init__.py | 3 ++- synapse/rest/voip.py | 59 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 synapse/config/voip.py create mode 100644 synapse/rest/voip.py (limited to 'synapse') 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..a47e81037a --- /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_uri = args.turn_uri + 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-uri", type=str, default=None, + help="The public URI 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/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/voip.py b/synapse/rest/voip.py new file mode 100644 index 0000000000..cba9b27e3b --- /dev/null +++ b/synapse/rest/voip.py @@ -0,0 +1,59 @@ +# -*- 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 + +from syutil.base64util import encode_base64 + +import hmac +import hashlib + + +class VoipRestServlet(RestServlet): + PATTERN = client_path_pattern("/voip/turnuris$") + + @defer.inlineCallbacks + def on_GET(self, request): + auth_user = yield self.auth.get_user_by_req(request) + + turnUri = self.hs.config.voip.turn_uri + turnSecret = self.hs.config.voip.turn_shared_secret + userLifetime = self.hs.config.voip.turn_user_lifetime + if not turnUri or not turnSecret or not userLifetime: + defer.returnValue( (200, {"uris": []}) ) + + 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) + password = encode_base64(mac.digest()) + + defer.returnValue( (200, { + 'username': username, + 'password': password, + 'ttl': userLifetime / 1000, + 'uris': [ + turnUri, + ] + }) ) + + def on_OPTIONS(self, request): + return (200, {}) + + +def register_servlets(hs, http_server): + VoipRestServlet(hs).register(http_server) -- cgit 1.5.1 From bc250a6afa68b7584f3b7b3aacb919df406da75a Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Tue, 23 Sep 2014 17:36:17 +0100 Subject: SYN-12: Implement auth for deletion by adding a 'delete_level' on the ops levels event SYN-12 # comment Auth has been added. --- synapse/api/auth.py | 34 +++++++++++++++++++++++++++++++--- synapse/handlers/room.py | 1 + synapse/storage/room.py | 10 +++++++--- synapse/storage/schema/delta/v4.sql | 4 ++++ 4 files changed, 43 insertions(+), 6 deletions(-) (limited to 'synapse') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 8f32191b57..fb14d9a2b3 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, RoomDeletionEvent, +) 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 == RoomDeletionEvent.TYPE: + yield self._check_deletion(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) @@ -321,6 +326,29 @@ class Auth(object): "You don't have permission to change that state" ) + @defer.inlineCallbacks + def _check_deletion(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 + + _, _, delete_level = yield self.store.get_ops_levels(event.room_id) + + if not delete_level: + delete_level = 50 + + if user_level < delete_level: + raise AuthError( + 403, + "You don't have permission to delete events" + ) + @defer.inlineCallbacks def _check_power_levels(self, event): for k, v in event.content.items(): diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index 5bc1280432..18597c694d 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -255,6 +255,7 @@ class RoomCreationHandler(BaseHandler): etype=RoomOpsPowerLevelsEvent.TYPE, ban_level=50, kick_level=50, + delete_level=50, ) return [ diff --git a/synapse/storage/room.py b/synapse/storage/room.py index 5adf8cdf1b..b1239a0f1a 100644 --- a/synapse/storage/room.py +++ b/synapse/storage/room.py @@ -27,7 +27,7 @@ import logging logger = logging.getLogger(__name__) -OpsLevel = collections.namedtuple("OpsLevel", ("ban_level", "kick_level")) +OpsLevel = collections.namedtuple("OpsLevel", ("ban_level", "kick_level", "delete_level")) class RoomStore(SQLBaseStore): @@ -189,7 +189,8 @@ class RoomStore(SQLBaseStore): def _get_ops_levels(self, txn, room_id): sql = ( - "SELECT ban_level, kick_level FROM room_ops_levels as r " + "SELECT ban_level, kick_level, delete_level " + "FROM room_ops_levels as r " "INNER JOIN current_state_events as c " "ON r.event_id = c.event_id " "WHERE c.room_id = ? " @@ -198,7 +199,7 @@ class RoomStore(SQLBaseStore): rows = txn.execute(sql, (room_id,)).fetchall() if len(rows) == 1: - return OpsLevel(rows[0][0], rows[0][1]) + return OpsLevel(rows[0][0], rows[0][1], rows[0][2]) else: return OpsLevel(None, None) @@ -326,6 +327,9 @@ class RoomStore(SQLBaseStore): if "ban_level" in event.content: content["ban_level"] = event.content["ban_level"] + if "delete_level" in event.content: + content["delete_level"] = event.content["delete_level"] + self._simple_insert_txn( txn, "room_ops_levels", diff --git a/synapse/storage/schema/delta/v4.sql b/synapse/storage/schema/delta/v4.sql index 2e2635317a..fc8bb5ad84 100644 --- a/synapse/storage/schema/delta/v4.sql +++ b/synapse/storage/schema/delta/v4.sql @@ -5,3 +5,7 @@ CREATE TABLE IF NOT EXISTS deletions ( CREATE INDEX IF NOT EXISTS deletions_event_id ON deletions (event_id); CREATE INDEX IF NOT EXISTS deletions_deletes ON deletions (deletes); + +ALTER TABLE room_ops_levels ADD COLUMN delete_level INTEGER; + +PRAGMA user_version = 4; -- cgit 1.5.1 From efea61dc507e1e02f3ac17417bd735a9d750bd12 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Tue, 23 Sep 2014 17:40:58 +0100 Subject: Rename 'pruned' to 'pruned_because' --- synapse/api/events/__init__.py | 2 +- synapse/storage/__init__.py | 2 +- synapse/storage/_base.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'synapse') diff --git a/synapse/api/events/__init__.py b/synapse/api/events/__init__.py index 910c990b33..d68629209d 100644 --- a/synapse/api/events/__init__.py +++ b/synapse/api/events/__init__.py @@ -59,7 +59,7 @@ class SynapseEvent(JsonEncodedObject): "age_ts", "prev_content", "prev_state", - "pruned", + "pruned_because", ] internal_keys = [ diff --git a/synapse/storage/__init__.py b/synapse/storage/__init__.py index 46d420b632..ac8ee89ab5 100644 --- a/synapse/storage/__init__.py +++ b/synapse/storage/__init__.py @@ -206,7 +206,7 @@ class DataStore(RoomMemberStore, RoomStore, unrec = { k: v for k, v in event.get_full_dict().items() - if k not in vals.keys() and k not in ["deleted", "pruned"] + if k not in vals.keys() and k not in ["deleted", "pruned_because"] } vals["unrecognized_keys"] = json.dumps(unrec) diff --git a/synapse/storage/_base.py b/synapse/storage/_base.py index 3aa610c85c..d64119a473 100644 --- a/synapse/storage/_base.py +++ b/synapse/storage/_base.py @@ -398,7 +398,7 @@ class SQLBaseStore(object): if del_evs: prune_event(ev) - ev.pruned = del_evs[0] + ev.pruned_because = del_evs[0] return events -- cgit 1.5.1 From c96ab4fcbb8b7cb61fffe46ef010ea2766d1dc63 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 23 Sep 2014 19:17:24 +0200 Subject: The config is not hierarchical --- synapse/rest/voip.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'synapse') diff --git a/synapse/rest/voip.py b/synapse/rest/voip.py index cba9b27e3b..1989a322cf 100644 --- a/synapse/rest/voip.py +++ b/synapse/rest/voip.py @@ -30,9 +30,9 @@ class VoipRestServlet(RestServlet): def on_GET(self, request): auth_user = yield self.auth.get_user_by_req(request) - turnUri = self.hs.config.voip.turn_uri - turnSecret = self.hs.config.voip.turn_shared_secret - userLifetime = self.hs.config.voip.turn_user_lifetime + turnUri = self.hs.config.turn_uri + turnSecret = self.hs.config.turn_shared_secret + userLifetime = self.hs.config.turn_user_lifetime if not turnUri or not turnSecret or not userLifetime: defer.returnValue( (200, {"uris": []}) ) -- cgit 1.5.1 From a7d53227de521a40acb682a7d5a204cc5c64eca4 Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Tue, 23 Sep 2014 19:07:16 +0100 Subject: Bugfix for older Pythons that lack hmac.compare_digest() --- synapse/rest/register.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'synapse') diff --git a/synapse/rest/register.py b/synapse/rest/register.py index 14d1ab018e..4935e323d9 100644 --- a/synapse/rest/register.py +++ b/synapse/rest/register.py @@ -30,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. @@ -169,7 +179,7 @@ class RegisterRestServlet(RestServlet): # have the buffer interface got = str(register_json["captcha_bypass_hmac"]) - if hmac.compare_digest(want, got): + if compare_digest(want, got): session["user"] = register_json["user"] defer.returnValue(None) else: -- cgit 1.5.1 From 4354590a69d2a9b8cfb0dd1e1bc677c118e68be4 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Wed, 24 Sep 2014 11:06:41 +0100 Subject: Add v4 deltas to current sql. --- synapse/storage/__init__.py | 1 + synapse/storage/schema/deletions.sql | 7 +++++++ synapse/storage/schema/im.sql | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 synapse/storage/schema/deletions.sql (limited to 'synapse') diff --git a/synapse/storage/__init__.py b/synapse/storage/__init__.py index ac8ee89ab5..4e8a54c8f7 100644 --- a/synapse/storage/__init__.py +++ b/synapse/storage/__init__.py @@ -57,6 +57,7 @@ SCHEMAS = [ "presence", "im", "room_aliases", + "deletions", ] diff --git a/synapse/storage/schema/deletions.sql b/synapse/storage/schema/deletions.sql new file mode 100644 index 0000000000..2e2635317a --- /dev/null +++ b/synapse/storage/schema/deletions.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS deletions ( + event_id TEXT NOT NULL, + deletes TEXT NOT NULL +); + +CREATE INDEX IF NOT EXISTS deletions_event_id ON deletions (event_id); +CREATE INDEX IF NOT EXISTS deletions_deletes ON deletions (deletes); diff --git a/synapse/storage/schema/im.sql b/synapse/storage/schema/im.sql index 6ffea51310..649d54b813 100644 --- a/synapse/storage/schema/im.sql +++ b/synapse/storage/schema/im.sql @@ -150,7 +150,8 @@ CREATE TABLE IF NOT EXISTS room_ops_levels( event_id TEXT NOT NULL, room_id TEXT NOT NULL, ban_level INTEGER, - kick_level INTEGER + kick_level INTEGER, + delete_level INTEGER ); CREATE INDEX IF NOT EXISTS room_ops_levels_event_id ON room_ops_levels(event_id); -- cgit 1.5.1 From 4e79b09dd97a1dda6b1ea87325302b95feb0a23e Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Wed, 24 Sep 2014 11:37:06 +0100 Subject: Fill out the prune_event method. --- synapse/api/events/utils.py | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) (limited to 'synapse') diff --git a/synapse/api/events/utils.py b/synapse/api/events/utils.py index dfefb2662a..3d8b9a1db1 100644 --- a/synapse/api/events/utils.py +++ b/synapse/api/events/utils.py @@ -13,7 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .room import RoomMemberEvent +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 @@ -27,12 +31,33 @@ def prune_event(event): # 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: - new_content = { - "membership": event.content["membership"] - } - else: - new_content = {} + 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", "delete_level") + elif event.type == RoomAliasesEvent.TYPE: + add_fields("aliases") event.content = new_content -- cgit 1.5.1 From 1e6c5b205c95c1f6941688c13a15314100a8470d Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Wed, 24 Sep 2014 13:29:20 +0100 Subject: Fix bug where we didn't correctly pull out the event_id of the deletion --- synapse/storage/__init__.py | 6 +++++- synapse/storage/_base.py | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) (limited to 'synapse') diff --git a/synapse/storage/__init__.py b/synapse/storage/__init__.py index 4e8a54c8f7..0d57073aa4 100644 --- a/synapse/storage/__init__.py +++ b/synapse/storage/__init__.py @@ -263,13 +263,17 @@ class DataStore(RoomMemberStore, RoomStore, @defer.inlineCallbacks def get_current_state(self, room_id, event_type=None, state_key=""): + del_sql = ( + "SELECT event_id FROM deletions WHERE deletes = e.event_id" + ) + sql = ( "SELECT e.*, (%(deleted)s) AS deleted FROM events as e " "INNER JOIN current_state_events as c ON e.event_id = c.event_id " "INNER JOIN state_events as s ON e.event_id = s.event_id " "WHERE c.room_id = ? " ) % { - "deleted": "e.event_id IN (SELECT deletes FROM deletions)", + "deleted": del_sql, } if event_type: diff --git a/synapse/storage/_base.py b/synapse/storage/_base.py index d64119a473..444e7628d1 100644 --- a/synapse/storage/_base.py +++ b/synapse/storage/_base.py @@ -403,9 +403,10 @@ class SQLBaseStore(object): return events def _has_been_deleted_txn(self, txn, event): - sql = "SELECT * FROM deletions WHERE deletes = ?" + sql = "SELECT event_id FROM deletions WHERE deletes = ?" txn.execute(sql, (event.event_id,)) - return len(txn.fetchall()) > 0 + result = txn.fetchone() + return result[0] if result else None class Table(object): -- cgit 1.5.1 From 7d9a84a445729e94b5f5aa445965b4e164442c7c Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Wed, 24 Sep 2014 14:18:08 +0100 Subject: Make deleting deletes not undelete --- synapse/storage/__init__.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) (limited to 'synapse') diff --git a/synapse/storage/__init__.py b/synapse/storage/__init__.py index 0d57073aa4..accb4359bd 100644 --- a/synapse/storage/__init__.py +++ b/synapse/storage/__init__.py @@ -246,20 +246,10 @@ class DataStore(RoomMemberStore, RoomStore, ) def _store_deletion(self, txn, event): - event_id = event.event_id - deletes = event.deletes - - # We check if this new delete deletes an old delete or has been - # deleted by a previous delete that we received out of order. - sql = "SELECT * FROM deletions WHERE event_id = ? OR deletes = ?" - txn.execute(sql, (deletes, event_id)) - - if txn.fetchall(): - sql = "DELETE FROM deletions WHERE event_id = ? OR deletes = ?" - txn.execute(sql, (deletes, event_id, )) - else: - sql = "INSERT INTO deletions (event_id, deletes) VALUES (?,?)" - txn.execute(sql, (event_id, deletes)) + txn.execute( + "INSERT INTO deletions (event_id, deletes) VALUES (?,?) OR IGNORE", + (event.event_id, event.deletes) + ) @defer.inlineCallbacks def get_current_state(self, room_id, event_type=None, state_key=""): -- cgit 1.5.1 From b42b0d3fe527313f7885ec3ac5e582a59c2c07fb Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 24 Sep 2014 15:29:24 +0200 Subject: Use standard base64 encoding with padding to get the same result as coturn. --- synapse/rest/voip.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'synapse') diff --git a/synapse/rest/voip.py b/synapse/rest/voip.py index 1989a322cf..bb0108cbd1 100644 --- a/synapse/rest/voip.py +++ b/synapse/rest/voip.py @@ -17,10 +17,10 @@ from twisted.internet import defer from base import RestServlet, client_path_pattern -from syutil.base64util import encode_base64 import hmac import hashlib +import base64 class VoipRestServlet(RestServlet): @@ -40,7 +40,10 @@ class VoipRestServlet(RestServlet): username = "%d:%s" % (expiry, auth_user.to_string()) mac = hmac.new(turnSecret, msg=username, digestmod=hashlib.sha1) - password = encode_base64(mac.digest()) + # 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, -- cgit 1.5.1 From 70899d3ab29cd57301fb135130c940f312bfe482 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Wed, 24 Sep 2014 15:27:59 +0100 Subject: Rename deletions to redactions --- synapse/api/auth.py | 18 +++++++++--------- synapse/api/events/__init__.py | 4 ++-- synapse/api/events/factory.py | 4 ++-- synapse/api/events/room.py | 6 +++--- synapse/api/events/utils.py | 4 ++-- synapse/handlers/room.py | 2 +- synapse/rest/room.py | 12 ++++++------ synapse/storage/__init__.py | 23 ++++++++++++----------- synapse/storage/_base.py | 18 +++++++++--------- synapse/storage/room.py | 8 ++++---- synapse/storage/roommember.py | 6 +++--- synapse/storage/schema/deletions.sql | 7 ------- synapse/storage/schema/delta/v4.sql | 11 ++++++----- synapse/storage/schema/im.sql | 2 +- synapse/storage/schema/redactions.sql | 8 ++++++++ synapse/storage/stream.py | 18 +++++++++--------- 16 files changed, 77 insertions(+), 74 deletions(-) delete mode 100644 synapse/storage/schema/deletions.sql create mode 100644 synapse/storage/schema/redactions.sql (limited to 'synapse') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index fb14d9a2b3..9c65d47fb4 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -20,7 +20,7 @@ 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, RoomDeletionEvent, + RoomMemberEvent, RoomPowerLevelsEvent, RoomRedactionEvent, ) from synapse.util.logutils import log_function @@ -72,8 +72,8 @@ class Auth(object): if event.type == RoomPowerLevelsEvent.TYPE: yield self._check_power_levels(event) - if event.type == RoomDeletionEvent.TYPE: - yield self._check_deletion(event) + if event.type == RoomRedactionEvent.TYPE: + yield self._check_redaction(event) defer.returnValue(True) else: @@ -327,7 +327,7 @@ class Auth(object): ) @defer.inlineCallbacks - def _check_deletion(self, event): + def _check_redaction(self, event): user_level = yield self.store.get_power_level( event.room_id, event.user_id, @@ -338,15 +338,15 @@ class Auth(object): else: user_level = 0 - _, _, delete_level = yield self.store.get_ops_levels(event.room_id) + _, _, redact_level = yield self.store.get_ops_levels(event.room_id) - if not delete_level: - delete_level = 50 + if not redact_level: + redact_level = 50 - if user_level < delete_level: + if user_level < redact_level: raise AuthError( 403, - "You don't have permission to delete events" + "You don't have permission to redact events" ) @defer.inlineCallbacks diff --git a/synapse/api/events/__init__.py b/synapse/api/events/__init__.py index d68629209d..0ceb703c74 100644 --- a/synapse/api/events/__init__.py +++ b/synapse/api/events/__init__.py @@ -59,7 +59,7 @@ class SynapseEvent(JsonEncodedObject): "age_ts", "prev_content", "prev_state", - "pruned_because", + "redacted_because", ] internal_keys = [ @@ -70,7 +70,7 @@ class SynapseEvent(JsonEncodedObject): "origin", "outlier", "power_level", - "deleted", + "redacted", ] required_keys = [ diff --git a/synapse/api/events/factory.py b/synapse/api/events/factory.py index c65ea8372b..0d94850cec 100644 --- a/synapse/api/events/factory.py +++ b/synapse/api/events/factory.py @@ -18,7 +18,7 @@ from synapse.api.events.room import ( InviteJoinEvent, RoomConfigEvent, RoomNameEvent, GenericEvent, RoomPowerLevelsEvent, RoomJoinRulesEvent, RoomOpsPowerLevelsEvent, RoomCreateEvent, RoomAddStateLevelEvent, RoomSendEventLevelEvent, - RoomDeletionEvent, + RoomRedactionEvent, ) from synapse.util.stringutils import random_string @@ -40,7 +40,7 @@ class EventFactory(object): RoomAddStateLevelEvent, RoomSendEventLevelEvent, RoomOpsPowerLevelsEvent, - RoomDeletionEvent, + RoomRedactionEvent, ] def __init__(self, hs): diff --git a/synapse/api/events/room.py b/synapse/api/events/room.py index 9861395556..cd936074fc 100644 --- a/synapse/api/events/room.py +++ b/synapse/api/events/room.py @@ -182,10 +182,10 @@ class RoomAliasesEvent(SynapseStateEvent): return {} -class RoomDeletionEvent(SynapseEvent): - TYPE = "m.room.deletion" +class RoomRedactionEvent(SynapseEvent): + TYPE = "m.room.redaction" - valid_keys = SynapseEvent.valid_keys + ["deletes"] + 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 index 3d8b9a1db1..c3a32be8c1 100644 --- a/synapse/api/events/utils.py +++ b/synapse/api/events/utils.py @@ -23,7 +23,7 @@ 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 "delete" an event. We want to remove all fields that + 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. """ @@ -55,7 +55,7 @@ def prune_event(event): elif event.type == RoomSendEventLevelEvent.TYPE: add_fields("level") elif event.type == RoomOpsPowerLevelsEvent.TYPE: - add_fields("kick_level", "ban_level", "delete_level") + add_fields("kick_level", "ban_level", "redact_level") elif event.type == RoomAliasesEvent.TYPE: add_fields("aliases") diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index 18597c694d..b7a9d77ca2 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -255,7 +255,7 @@ class RoomCreationHandler(BaseHandler): etype=RoomOpsPowerLevelsEvent.TYPE, ban_level=50, kick_level=50, - delete_level=50, + redact_level=50, ) return [ diff --git a/synapse/rest/room.py b/synapse/rest/room.py index 85a1d2eae3..399084ae2a 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, RoomDeletionEvent +from synapse.api.events.room import RoomMemberEvent, RoomRedactionEvent from synapse.api.constants import Membership import json @@ -430,9 +430,9 @@ class RoomMembershipRestServlet(RestServlet): self.txns.store_client_transaction(request, txn_id, response) defer.returnValue(response) -class RoomDeleteEventRestServlet(RestServlet): +class RoomRedactEventRestServlet(RestServlet): def register(self, http_server): - PATTERN = ("/rooms/(?P[^/]*)/delete/(?P[^/]*)") + PATTERN = ("/rooms/(?P[^/]*)/redact/(?P[^/]*)") register_txn_path(self, PATTERN, http_server) @defer.inlineCallbacks @@ -441,11 +441,11 @@ class RoomDeleteEventRestServlet(RestServlet): content = _parse_json(request) event = self.event_factory.create_event( - etype=RoomDeletionEvent.TYPE, + etype=RoomRedactionEvent.TYPE, room_id=urllib.unquote(room_id), user_id=user.to_string(), content=content, - deletes=event_id, + redacts=event_id, ) msg_handler = self.handlers.message_handler @@ -520,4 +520,4 @@ def register_servlets(hs, http_server): PublicRoomListRestServlet(hs).register(http_server) RoomStateRestServlet(hs).register(http_server) RoomInitialSyncRestServlet(hs).register(http_server) - RoomDeleteEventRestServlet(hs).register(http_server) + RoomRedactEventRestServlet(hs).register(http_server) diff --git a/synapse/storage/__init__.py b/synapse/storage/__init__.py index accb4359bd..fcf8b4d183 100644 --- a/synapse/storage/__init__.py +++ b/synapse/storage/__init__.py @@ -24,7 +24,7 @@ from synapse.api.events.room import ( RoomAddStateLevelEvent, RoomSendEventLevelEvent, RoomOpsPowerLevelsEvent, - RoomDeletionEvent, + RoomRedactionEvent, ) from synapse.util.logutils import log_function @@ -57,7 +57,7 @@ SCHEMAS = [ "presence", "im", "room_aliases", - "deletions", + "redactions", ] @@ -184,8 +184,8 @@ class DataStore(RoomMemberStore, RoomStore, self._store_send_event_level(txn, event) elif event.type == RoomOpsPowerLevelsEvent.TYPE: self._store_ops_level(txn, event) - elif event.type == RoomDeletionEvent.TYPE: - self._store_deletion(txn, event) + elif event.type == RoomRedactionEvent.TYPE: + self._store_redaction(txn, event) vals = { "topological_ordering": event.depth, @@ -207,7 +207,7 @@ class DataStore(RoomMemberStore, RoomStore, unrec = { k: v for k, v in event.get_full_dict().items() - if k not in vals.keys() and k not in ["deleted", "pruned_because"] + if k not in vals.keys() and k not in ["redacted", "redacted_because"] } vals["unrecognized_keys"] = json.dumps(unrec) @@ -245,25 +245,26 @@ class DataStore(RoomMemberStore, RoomStore, } ) - def _store_deletion(self, txn, event): + def _store_redaction(self, txn, event): txn.execute( - "INSERT INTO deletions (event_id, deletes) VALUES (?,?) OR IGNORE", - (event.event_id, event.deletes) + "INSERT OR IGNORE INTO redactions " + "(event_id, redacts) VALUES (?,?)", + (event.event_id, event.redacts) ) @defer.inlineCallbacks def get_current_state(self, room_id, event_type=None, state_key=""): del_sql = ( - "SELECT event_id FROM deletions WHERE deletes = e.event_id" + "SELECT event_id FROM redactions WHERE redacts = e.event_id" ) sql = ( - "SELECT e.*, (%(deleted)s) AS deleted FROM events as e " + "SELECT e.*, (%(redacted)s) AS redacted FROM events as e " "INNER JOIN current_state_events as c ON e.event_id = c.event_id " "INNER JOIN state_events as s ON e.event_id = s.event_id " "WHERE c.room_id = ? " ) % { - "deleted": del_sql, + "redacted": del_sql, } if event_type: diff --git a/synapse/storage/_base.py b/synapse/storage/_base.py index 444e7628d1..889de2bedc 100644 --- a/synapse/storage/_base.py +++ b/synapse/storage/_base.py @@ -383,14 +383,14 @@ class SQLBaseStore(object): prev = self._parse_event_from_row(prevs[0]) ev.prev_content = prev.content - if not hasattr(ev, "deleted"): - logger.debug("Doesn't have deleted key: %s", ev) - ev.deleted = self._has_been_deleted_txn(txn, ev) + if not hasattr(ev, "redacted"): + logger.debug("Doesn't have redacted key: %s", ev) + ev.redacted = self._has_been_redacted_txn(txn, ev) - if ev.deleted: - # Get the deletion event. + if ev.redacted: + # Get the redaction event. sql = "SELECT * FROM events WHERE event_id = ?" - txn.execute(sql, (ev.deleted,)) + txn.execute(sql, (ev.redacted,)) del_evs = self._parse_events_txn( txn, self.cursor_to_dict(txn) @@ -398,12 +398,12 @@ class SQLBaseStore(object): if del_evs: prune_event(ev) - ev.pruned_because = del_evs[0] + ev.redacted_because = del_evs[0] return events - def _has_been_deleted_txn(self, txn, event): - sql = "SELECT event_id FROM deletions WHERE deletes = ?" + def _has_been_redacted_txn(self, txn, event): + sql = "SELECT event_id FROM redactions WHERE redacts = ?" txn.execute(sql, (event.event_id,)) result = txn.fetchone() return result[0] if result else None diff --git a/synapse/storage/room.py b/synapse/storage/room.py index b1239a0f1a..8cd46334cf 100644 --- a/synapse/storage/room.py +++ b/synapse/storage/room.py @@ -27,7 +27,7 @@ import logging logger = logging.getLogger(__name__) -OpsLevel = collections.namedtuple("OpsLevel", ("ban_level", "kick_level", "delete_level")) +OpsLevel = collections.namedtuple("OpsLevel", ("ban_level", "kick_level", "redact_level")) class RoomStore(SQLBaseStore): @@ -189,7 +189,7 @@ class RoomStore(SQLBaseStore): def _get_ops_levels(self, txn, room_id): sql = ( - "SELECT ban_level, kick_level, delete_level " + "SELECT ban_level, kick_level, redact_level " "FROM room_ops_levels as r " "INNER JOIN current_state_events as c " "ON r.event_id = c.event_id " @@ -327,8 +327,8 @@ class RoomStore(SQLBaseStore): if "ban_level" in event.content: content["ban_level"] = event.content["ban_level"] - if "delete_level" in event.content: - content["delete_level"] = event.content["delete_level"] + if "redact_level" in event.content: + content["redact_level"] = event.content["redact_level"] self._simple_insert_txn( txn, diff --git a/synapse/storage/roommember.py b/synapse/storage/roommember.py index 97222da571..84c462330b 100644 --- a/synapse/storage/roommember.py +++ b/synapse/storage/roommember.py @@ -183,18 +183,18 @@ class RoomMemberStore(SQLBaseStore): def _get_members_query_txn(self, txn, where_clause, where_values): del_sql = ( - "SELECT event_id FROM deletions WHERE deletes = e.event_id" + "SELECT event_id FROM redactions WHERE redacts = e.event_id" ) sql = ( - "SELECT e.*, (%(deleted)s) AS deleted FROM events as e " + "SELECT e.*, (%(redacted)s) AS redacted FROM events as e " "INNER JOIN room_memberships as m " "ON e.event_id = m.event_id " "INNER JOIN current_state_events as c " "ON m.event_id = c.event_id " "WHERE %(where)s " ) % { - "deleted": del_sql, + "redacted": del_sql, "where": where_clause, } diff --git a/synapse/storage/schema/deletions.sql b/synapse/storage/schema/deletions.sql deleted file mode 100644 index 2e2635317a..0000000000 --- a/synapse/storage/schema/deletions.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE IF NOT EXISTS deletions ( - event_id TEXT NOT NULL, - deletes TEXT NOT NULL -); - -CREATE INDEX IF NOT EXISTS deletions_event_id ON deletions (event_id); -CREATE INDEX IF NOT EXISTS deletions_deletes ON deletions (deletes); diff --git a/synapse/storage/schema/delta/v4.sql b/synapse/storage/schema/delta/v4.sql index fc8bb5ad84..25d2ead450 100644 --- a/synapse/storage/schema/delta/v4.sql +++ b/synapse/storage/schema/delta/v4.sql @@ -1,11 +1,12 @@ -CREATE TABLE IF NOT EXISTS deletions ( +CREATE TABLE IF NOT EXISTS redactions ( event_id TEXT NOT NULL, - deletes TEXT NOT NULL + redacts TEXT NOT NULL, + CONSTRAINT ev_uniq UNIQUE (event_id) ); -CREATE INDEX IF NOT EXISTS deletions_event_id ON deletions (event_id); -CREATE INDEX IF NOT EXISTS deletions_deletes ON deletions (deletes); +CREATE INDEX IF NOT EXISTS redactions_event_id ON redactions (event_id); +CREATE INDEX IF NOT EXISTS redactions_redacts ON redactions (redacts); -ALTER TABLE room_ops_levels ADD COLUMN delete_level INTEGER; +ALTER TABLE room_ops_levels ADD COLUMN redact_level INTEGER; PRAGMA user_version = 4; diff --git a/synapse/storage/schema/im.sql b/synapse/storage/schema/im.sql index 649d54b813..3aa83f5c8c 100644 --- a/synapse/storage/schema/im.sql +++ b/synapse/storage/schema/im.sql @@ -151,7 +151,7 @@ CREATE TABLE IF NOT EXISTS room_ops_levels( room_id TEXT NOT NULL, ban_level INTEGER, kick_level INTEGER, - delete_level INTEGER + redact_level INTEGER ); CREATE INDEX IF NOT EXISTS room_ops_levels_event_id ON room_ops_levels(event_id); diff --git a/synapse/storage/schema/redactions.sql b/synapse/storage/schema/redactions.sql new file mode 100644 index 0000000000..4c2829d05d --- /dev/null +++ b/synapse/storage/schema/redactions.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS redactions ( + event_id TEXT NOT NULL, + redacts TEXT NOT NULL, + CONSTRAINT ev_uniq UNIQUE (event_id) +); + +CREATE INDEX IF NOT EXISTS redactions_event_id ON redactions (event_id); +CREATE INDEX IF NOT EXISTS redactions_redacts ON redactions (redacts); diff --git a/synapse/storage/stream.py b/synapse/storage/stream.py index aaac0aae30..91baa18319 100644 --- a/synapse/storage/stream.py +++ b/synapse/storage/stream.py @@ -158,7 +158,7 @@ class StreamStore(SQLBaseStore): ) del_sql = ( - "SELECT event_id FROM deletions WHERE deletes = e.event_id" + "SELECT event_id FROM redactions WHERE redacts = e.event_id" ) if limit: @@ -175,14 +175,14 @@ class StreamStore(SQLBaseStore): return sql = ( - "SELECT *, (%(deleted)s) AS deleted FROM events AS e WHERE " + "SELECT *, (%(redacted)s) AS redacted FROM events AS e WHERE " "((room_id IN (%(current)s)) OR " "(event_id IN (%(invites)s))) " "AND e.stream_ordering > ? AND e.stream_ordering <= ? " "AND e.outlier = 0 " "ORDER BY stream_ordering ASC LIMIT %(limit)d " ) % { - "deleted": del_sql, + "redacted": del_sql, "current": current_room_membership_sql, "invites": membership_sql, "limit": limit @@ -230,15 +230,15 @@ class StreamStore(SQLBaseStore): limit_str = "" del_sql = ( - "SELECT event_id FROM deletions WHERE deletes = events.event_id" + "SELECT event_id FROM redactions WHERE redacts = events.event_id" ) sql = ( - "SELECT *, (%(deleted)s) AS deleted FROM events " + "SELECT *, (%(redacted)s) AS redacted FROM events " "WHERE outlier = 0 AND room_id = ? AND %(bounds)s " "ORDER BY topological_ordering %(order)s, stream_ordering %(order)s %(limit)s " ) % { - "deleted": del_sql, + "redacted": del_sql, "bounds": bounds, "order": order, "limit": limit_str @@ -272,15 +272,15 @@ class StreamStore(SQLBaseStore): # TODO (erikj): Handle compressed feedback del_sql = ( - "SELECT event_id FROM deletions WHERE deletes = events.event_id" + "SELECT event_id FROM redactions WHERE redacts = events.event_id" ) sql = ( - "SELECT *, (%(deleted)s) AS deleted FROM events " + "SELECT *, (%(redacted)s) AS redacted FROM events " "WHERE room_id = ? AND stream_ordering <= ? " "ORDER BY topological_ordering DESC, stream_ordering DESC LIMIT ? " ) % { - "deleted": del_sql, + "redacted": del_sql, } rows = yield self._execute_and_decode( -- cgit 1.5.1 From 5383ba55870079076277ee6e83458f6cd7ceee85 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 24 Sep 2014 16:01:36 +0100 Subject: rename endpoint to better reflect what it is and allow specifying multiple uris --- synapse/config/voip.py | 6 +++--- synapse/rest/voip.py | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) (limited to 'synapse') diff --git a/synapse/config/voip.py b/synapse/config/voip.py index a47e81037a..3a211ae6b6 100644 --- a/synapse/config/voip.py +++ b/synapse/config/voip.py @@ -19,7 +19,7 @@ class VoipConfig(Config): def __init__(self, args): super(VoipConfig, self).__init__(args) - self.turn_uri = args.turn_uri + self.turn_uris = args.turn_uris.split(",") self.turn_shared_secret = args.turn_shared_secret self.turn_user_lifetime = args.turn_user_lifetime @@ -28,8 +28,8 @@ class VoipConfig(Config): super(VoipConfig, cls).add_arguments(parser) group = parser.add_argument_group("voip") group.add_argument( - "--turn-uri", type=str, default=None, - help="The public URI of the TURN server to give to clients" + "--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, diff --git a/synapse/rest/voip.py b/synapse/rest/voip.py index bb0108cbd1..31f3fd100d 100644 --- a/synapse/rest/voip.py +++ b/synapse/rest/voip.py @@ -24,13 +24,13 @@ import base64 class VoipRestServlet(RestServlet): - PATTERN = client_path_pattern("/voip/turnuris$") + PATTERN = client_path_pattern("/voip/turnServers$") @defer.inlineCallbacks def on_GET(self, request): auth_user = yield self.auth.get_user_by_req(request) - turnUri = self.hs.config.turn_uri + turnUris = self.hs.config.turn_uris turnSecret = self.hs.config.turn_shared_secret userLifetime = self.hs.config.turn_user_lifetime if not turnUri or not turnSecret or not userLifetime: @@ -49,9 +49,7 @@ class VoipRestServlet(RestServlet): 'username': username, 'password': password, 'ttl': userLifetime / 1000, - 'uris': [ - turnUri, - ] + 'uris': turnUris, }) ) def on_OPTIONS(self, request): -- cgit 1.5.1 From 455365113878632774d12039cb6ab362d6d16416 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 24 Sep 2014 17:04:33 +0200 Subject: Oops --- synapse/rest/voip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'synapse') diff --git a/synapse/rest/voip.py b/synapse/rest/voip.py index 31f3fd100d..a3a8842cb6 100644 --- a/synapse/rest/voip.py +++ b/synapse/rest/voip.py @@ -33,7 +33,7 @@ class VoipRestServlet(RestServlet): turnUris = self.hs.config.turn_uris turnSecret = self.hs.config.turn_shared_secret userLifetime = self.hs.config.turn_user_lifetime - if not turnUri or not turnSecret or not userLifetime: + if not turnUris or not turnSecret or not userLifetime: defer.returnValue( (200, {"uris": []}) ) expiry = self.hs.get_clock().time_msec() + userLifetime -- cgit 1.5.1 From 87deaf1658532b679f969ecf8067bde823f3f1bb Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Wed, 24 Sep 2014 16:15:58 +0100 Subject: SYN-70: Fix typo --- synapse/api/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'synapse') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 8f32191b57..80fe5d925d 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -376,7 +376,7 @@ class Auth(object): 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, ) -- cgit 1.5.1 From 327dcc98e378afb6c0d340017268dafbc9d0af17 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Wed, 24 Sep 2014 16:19:29 +0100 Subject: SYN-70: And fix another bug where I can't type --- synapse/api/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'synapse') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 80fe5d925d..5321864d51 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -372,7 +372,7 @@ 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: -- cgit 1.5.1 From 7dc7c53029fccbccf291ca4c299fccfdeb8e19fb Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 24 Sep 2014 17:28:47 +0200 Subject: The REST API spec only alows for returning a single server so name the endpoint appropriately. --- synapse/rest/voip.py | 2 +- webclient/components/matrix/matrix-service.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'synapse') diff --git a/synapse/rest/voip.py b/synapse/rest/voip.py index a3a8842cb6..7260ff0e8e 100644 --- a/synapse/rest/voip.py +++ b/synapse/rest/voip.py @@ -24,7 +24,7 @@ import base64 class VoipRestServlet(RestServlet): - PATTERN = client_path_pattern("/voip/turnServers$") + PATTERN = client_path_pattern("/voip/turnServer$") @defer.inlineCallbacks def on_GET(self, request): diff --git a/webclient/components/matrix/matrix-service.js b/webclient/components/matrix/matrix-service.js index 69e6caccd3..cb827a0b4d 100644 --- a/webclient/components/matrix/matrix-service.js +++ b/webclient/components/matrix/matrix-service.js @@ -765,7 +765,7 @@ angular.module('matrixService', []) }, getTurnServer: function() { - return doRequest("GET", "/voip/turnServers"); + return doRequest("GET", "/voip/turnServer"); } }; -- cgit 1.5.1 From 72eb360f2d98b6aead026b9911080d54983b99fb Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Wed, 24 Sep 2014 16:58:39 +0100 Subject: Don't set the room name to be the room alias on room creation if the client didn't supply a name --- synapse/handlers/room.py | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'synapse') diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index 5bc1280432..4d8727e8af 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -145,17 +145,6 @@ class RoomCreationHandler(BaseHandler): content={"name": name}, ) - 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: -- cgit 1.5.1 From a31bf7777694d794d8e861c2bfede4a8ebb8849e Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 25 Sep 2014 11:24:49 +0200 Subject: Make turn server endpoint return an empty object if no turn servers to match the normal response. Don't break if the turn_uris option isn't present. --- synapse/config/voip.py | 2 +- synapse/rest/voip.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'synapse') diff --git a/synapse/config/voip.py b/synapse/config/voip.py index 3a211ae6b6..c5131d9bcd 100644 --- a/synapse/config/voip.py +++ b/synapse/config/voip.py @@ -19,7 +19,7 @@ class VoipConfig(Config): def __init__(self, args): super(VoipConfig, self).__init__(args) - self.turn_uris = args.turn_uris.split(",") + 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 diff --git a/synapse/rest/voip.py b/synapse/rest/voip.py index 7260ff0e8e..2e4627606f 100644 --- a/synapse/rest/voip.py +++ b/synapse/rest/voip.py @@ -34,7 +34,7 @@ class VoipRestServlet(RestServlet): 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, {"uris": []}) ) + defer.returnValue( (200, {}) ) expiry = self.hs.get_clock().time_msec() + userLifetime username = "%d:%s" % (expiry, auth_user.to_string()) -- cgit 1.5.1 From 1ca51c8586a4e96fe8d0fdfe85fc668933d87578 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Thu, 25 Sep 2014 13:01:05 +0100 Subject: SYN-46: An invite received from fedearation didn't wake up the event stream for the invited user. --- synapse/handlers/federation.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'synapse') 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: -- cgit 1.5.1 From ba87eb675334b22f312849186089a1d53dd0fbb6 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Thu, 25 Sep 2014 14:45:15 +0100 Subject: Fix bug where we tried to insert state events with null state key --- synapse/storage/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'synapse') diff --git a/synapse/storage/__init__.py b/synapse/storage/__init__.py index 66658f6721..1aaef3f493 100644 --- a/synapse/storage/__init__.py +++ b/synapse/storage/__init__.py @@ -217,7 +217,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, -- cgit 1.5.1 From c818aa13eb47c9d30da26645850a2af2672edf68 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Thu, 25 Sep 2014 15:51:21 +0100 Subject: Add LIMIT to scalar subquery --- synapse/storage/__init__.py | 3 ++- synapse/storage/roommember.py | 3 ++- synapse/storage/stream.py | 9 ++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) (limited to 'synapse') diff --git a/synapse/storage/__init__.py b/synapse/storage/__init__.py index fcf8b4d183..38f5ec0ac5 100644 --- a/synapse/storage/__init__.py +++ b/synapse/storage/__init__.py @@ -255,7 +255,8 @@ class DataStore(RoomMemberStore, RoomStore, @defer.inlineCallbacks def get_current_state(self, room_id, event_type=None, state_key=""): del_sql = ( - "SELECT event_id FROM redactions WHERE redacts = e.event_id" + "SELECT event_id FROM redactions WHERE redacts = e.event_id " + "LIMIT 1" ) sql = ( diff --git a/synapse/storage/roommember.py b/synapse/storage/roommember.py index 84c462330b..958e730591 100644 --- a/synapse/storage/roommember.py +++ b/synapse/storage/roommember.py @@ -183,7 +183,8 @@ class RoomMemberStore(SQLBaseStore): def _get_members_query_txn(self, txn, where_clause, where_values): del_sql = ( - "SELECT event_id FROM redactions WHERE redacts = e.event_id" + "SELECT event_id FROM redactions WHERE redacts = e.event_id " + "LIMIT 1" ) sql = ( diff --git a/synapse/storage/stream.py b/synapse/storage/stream.py index 91baa18319..d61f909939 100644 --- a/synapse/storage/stream.py +++ b/synapse/storage/stream.py @@ -158,7 +158,8 @@ class StreamStore(SQLBaseStore): ) del_sql = ( - "SELECT event_id FROM redactions WHERE redacts = e.event_id" + "SELECT event_id FROM redactions WHERE redacts = e.event_id " + "LIMIT 1" ) if limit: @@ -230,7 +231,8 @@ class StreamStore(SQLBaseStore): limit_str = "" del_sql = ( - "SELECT event_id FROM redactions WHERE redacts = events.event_id" + "SELECT event_id FROM redactions WHERE redacts = events.event_id " + "LIMIT 1" ) sql = ( @@ -272,7 +274,8 @@ class StreamStore(SQLBaseStore): # TODO (erikj): Handle compressed feedback del_sql = ( - "SELECT event_id FROM redactions WHERE redacts = events.event_id" + "SELECT event_id FROM redactions WHERE redacts = events.event_id " + "LIMIT 1" ) sql = ( -- cgit 1.5.1 From 69ddec65895d33a25dbaa5991462e32442dbe6e5 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Thu, 25 Sep 2014 16:49:02 +0100 Subject: Don't strip of False values from events when serializing --- synapse/api/events/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'synapse') diff --git a/synapse/api/events/__init__.py b/synapse/api/events/__init__.py index 0ceb703c74..db6dff5762 100644 --- a/synapse/api/events/__init__.py +++ b/synapse/api/events/__init__.py @@ -22,7 +22,7 @@ def serialize_event(hs, e): if not isinstance(e, SynapseEvent): return e - d = {k: v for k, v in e.get_dict().items() if v is not None or v is not False} + d = {k: v for k, v in e.get_dict().items() if v is not None} if "age_ts" in d: d["age"] = int(hs.get_clock().time_msec()) - d["age_ts"] del d["age_ts"] -- cgit 1.5.1 From dcadfbbd4a829a097c49c7b919a9429c3ca05de5 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Thu, 25 Sep 2014 16:59:26 +0100 Subject: Don't strip out null's in serialized events, as that is not need anymore and it's not in the spec (yet) --- synapse/api/events/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'synapse') diff --git a/synapse/api/events/__init__.py b/synapse/api/events/__init__.py index db6dff5762..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 = {k: v for k, v in e.get_dict().items() if v is not None} + # 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"] -- cgit 1.5.1 From 3b0fb6aae8d3c886fbdfcd72e4bf94f81eee2daa Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Thu, 25 Sep 2014 18:05:06 +0100 Subject: Bump version and changelog --- CHANGES.rst | 22 ++++++++++++++++++++++ VERSION | 2 +- synapse/__init__.py | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) (limited to 'synapse') diff --git a/CHANGES.rst b/CHANGES.rst index 400ded0f15..00e7cf2c6d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,25 @@ +Changes in synapse 0.3.4 (2014-09-25) +===================================== +This version adds support for using a TURN server. See docs/turn-howto.rst on +how to set one up. + +Homeserver: + * Add support for redaction of messages. + * Fix bug where inviting a user on a remote home server could take up to + 20-30s. + * Implement a get current room state API. + * Add support specifying and retrieving turn server configuration. + +Webclient: + * Add support for using TURN for VoIP calls. + * Show display name change messages. + * Fix bug where the client didn't get the state of a newly joined room + until after it has been refreshed. + * Fix bugs with tab complete. + * Fix bug where holding down the down arrow caused chrome to chew 100% CPU. + * Fix bug where desktop notifications occasionally used "Undefined" as the + display name. + Changes in synapse 0.3.3 (2014-09-22) ===================================== diff --git a/VERSION b/VERSION index 1c09c74e22..42045acae2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.3 +0.3.4 diff --git a/synapse/__init__.py b/synapse/__init__.py index bba551b2c4..a340a5db66 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.3" +__version__ = "0.3.4" -- cgit 1.5.1 From ec5fb77a66131382a179c349cf239859be7d8f4e Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 25 Sep 2014 19:18:32 +0200 Subject: Just use a yaml list for turn servers --- docs/turn-howto.rst | 4 ++-- synapse/config/voip.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'synapse') diff --git a/docs/turn-howto.rst b/docs/turn-howto.rst index 2cb0c4170d..82b59538c8 100644 --- a/docs/turn-howto.rst +++ b/docs/turn-howto.rst @@ -62,8 +62,8 @@ synapse Setup Your home server configuration file needs the following extra keys: - 1. "turn_uris": This needs to be a comma-separated - list of public-facing URIs for your TURN server to be given out + 1. "turn_uris": This needs to be a yaml list + of public-facing URIs for your TURN server to be given out to your clients. Add separate entries for each transport your TURN server supports. diff --git a/synapse/config/voip.py b/synapse/config/voip.py index c5131d9bcd..3a51664f46 100644 --- a/synapse/config/voip.py +++ b/synapse/config/voip.py @@ -19,7 +19,7 @@ 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_uris = args.turn_uris self.turn_shared_secret = args.turn_shared_secret self.turn_user_lifetime = args.turn_user_lifetime -- cgit 1.5.1