diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 2473a2b2b..c77f52dc3 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -17,9 +17,10 @@ from twisted.internet import defer -from synapse.api.constants import Membership +from synapse.api.constants import Membership, JoinRules from synapse.api.errors import AuthError, StoreError, Codes from synapse.api.events.room import RoomMemberEvent +from synapse.util.logutils import log_function import logging @@ -47,13 +48,20 @@ class Auth(object): if event.type == RoomMemberEvent.TYPE: allowed = yield self.is_membership_change_allowed(event) defer.returnValue(allowed) + return + + self._check_joined_room( + member=snapshot.membership_state, + user_id=snapshot.user_id, + room_id=snapshot.room_id, + ) + + if hasattr(event, "state_key"): + yield self._can_add_state(event) else: - self._check_joined_room( - member=snapshot.membership_state, - user_id=snapshot.user_id, - room_id=snapshot.room_id, - ) - defer.returnValue(True) + yield self._can_send_event(event) + + defer.returnValue(True) else: raise AuthError(500, "Unknown event: %s" % event) except AuthError as e: @@ -111,7 +119,14 @@ class Auth(object): membership = event.content["membership"] + join_rule = yield self.store.get_room_join_rule(event.room_id) + if not join_rule: + join_rule = JoinRules.INVITE + if Membership.INVITE == membership: + # TODO (erikj): We should probably handle this more intelligently + # PRIVATE join rules. + # Invites are valid iff caller is in the room and target isn't. if not caller_in_room: # caller isn't joined raise AuthError(403, "You are not in room %s." % event.room_id) @@ -124,11 +139,18 @@ class Auth(object): # joined: It's a NOOP if event.user_id != target_user_id: raise AuthError(403, "Cannot force another user to join.") - elif room.is_public: - pass # anyone can join public rooms. - elif (not caller or caller.membership not in - [Membership.INVITE, Membership.JOIN]): - raise AuthError(403, "You are not invited to this room.") + elif join_rule == JoinRules.PUBLIC or room.is_public: + pass + elif join_rule == JoinRules.INVITE: + if ( + not caller or caller.membership not in + [Membership.INVITE, Membership.JOIN] + ): + raise AuthError(403, "You are not invited to this room.") + else: + # TODO (erikj): may_join list + # TODO (erikj): private rooms + raise AuthError(403, "You are not allowed to join this room") elif Membership.LEAVE == membership: if not caller_in_room: # trying to leave a room you aren't joined raise AuthError(403, "You are not in room %s." % event.room_id) @@ -176,3 +198,53 @@ class Auth(object): except StoreError: raise AuthError(403, "Unrecognised access token.", errcode=Codes.UNKNOWN_TOKEN) + + @defer.inlineCallbacks + @log_function + def _can_send_event(self, event): + send_level = yield self.store.get_send_event_level(event.room_id) + + if send_level: + send_level = int(send_level) + else: + send_level = 0 + + 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 + + if user_level < send_level: + raise AuthError( + 403, "You don't have permission to post to the room" + ) + + defer.returnValue(True) + + @defer.inlineCallbacks + def _can_add_state(self, event): + add_level = yield self.store.get_add_state_level(event.room_id) + + if not add_level: + defer.returnValue(True) + + add_level = int(add_level) + + user_level = yield self.store.get_power_level( + event.room_id, + event.user_id, + ) + + user_level = int(user_level) + + if user_level < add_level: + raise AuthError( + 403, "You don't have permission to add state to the room" + ) + + defer.returnValue(True) diff --git a/synapse/api/events/factory.py b/synapse/api/events/factory.py index 7c1259d61..56180899b 100644 --- a/synapse/api/events/factory.py +++ b/synapse/api/events/factory.py @@ -16,8 +16,8 @@ from synapse.api.events.room import ( RoomTopicEvent, MessageEvent, RoomMemberEvent, FeedbackEvent, InviteJoinEvent, RoomConfigEvent, RoomNameEvent, GenericEvent, - RoomPowerLevelsEvent, RoomDefaultLevelEvent, RoomJoinRulesEvent, - RoomCreateEvent, + RoomPowerLevelsEvent, RoomJoinRulesEvent, + RoomCreateEvent, RoomAddStateLevelEvent, RoomSendEventLevelEvent ) from synapse.util.stringutils import random_string @@ -34,9 +34,10 @@ class EventFactory(object): InviteJoinEvent, RoomConfigEvent, RoomPowerLevelsEvent, - RoomDefaultLevelEvent, RoomJoinRulesEvent, RoomCreateEvent, + RoomAddStateLevelEvent, + RoomSendEventLevelEvent, ] def __init__(self, hs): diff --git a/synapse/api/events/room.py b/synapse/api/events/room.py index b63529bb3..6b431e24e 100644 --- a/synapse/api/events/room.py +++ b/synapse/api/events/room.py @@ -155,8 +155,15 @@ class RoomPowerLevelsEvent(SynapseStateEvent): return {} -class RoomDefaultLevelEvent(SynapseStateEvent): - TYPE = "m.room.default_level" +class RoomAddStateLevelEvent(SynapseStateEvent): + TYPE = "m.room.add_state_level" + + def get_content_template(self): + return {} + + +class RoomSendEventLevelEvent(SynapseStateEvent): + TYPE = "m.room.send_event_level" def get_content_template(self): return {} diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index 11afd34ae..dace364ea 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -21,7 +21,8 @@ from synapse.api.constants import Membership, JoinRules from synapse.api.errors import StoreError, SynapseError from synapse.api.events.room import ( RoomMemberEvent, RoomCreateEvent, RoomPowerLevelsEvent, - RoomJoinRulesEvent, RoomDefaultLevelEvent, + RoomJoinRulesEvent, RoomAddStateLevelEvent, + RoomSendEventLevelEvent, ) from synapse.util import stringutils from ._base import BaseRoomHandler @@ -152,7 +153,7 @@ class RoomCreationHandler(BaseRoomHandler): creation_event = self.event_factory.create_event( etype=RoomCreateEvent.TYPE, - content={"creator": creator.to_string()}, + content={"creator": creator.to_string(), "default": 0}, **event_keys ) @@ -162,12 +163,6 @@ class RoomCreationHandler(BaseRoomHandler): **event_keys ) - default_level_event = self.event_factory.create_event( - etype=RoomDefaultLevelEvent.TYPE, - content={"default_level": 0}, - **event_keys - ) - join_rule = JoinRules.PUBLIC if is_public else JoinRules.INVITE join_rules_event = self.event_factory.create_event( etype=RoomJoinRulesEvent.TYPE, @@ -175,7 +170,25 @@ class RoomCreationHandler(BaseRoomHandler): **event_keys ) - return [creation_event, power_levels_event, default_level_event, join_rules_event] + add_state_event = self.event_factory.create_event( + etype=RoomAddStateLevelEvent.TYPE, + content={"level": 10}, + **event_keys + ) + + send_event = self.event_factory.create_event( + etype=RoomSendEventLevelEvent.TYPE, + content={"level": 0}, + **event_keys + ) + + return [ + creation_event, + power_levels_event, + join_rules_event, + add_state_event, + send_event, + ] class RoomMemberHandler(BaseRoomHandler): diff --git a/synapse/storage/__init__.py b/synapse/storage/__init__.py index 204a24356..3d5e5049f 100644 --- a/synapse/storage/__init__.py +++ b/synapse/storage/__init__.py @@ -21,7 +21,8 @@ from synapse.api.events.room import ( RoomNameEvent, RoomJoinRulesEvent, RoomPowerLevelsEvent, - RoomDefaultLevelEvent, + RoomAddStateLevelEvent, + RoomSendEventLevelEvent, ) from synapse.util.logutils import log_function @@ -125,7 +126,7 @@ class DataStore(RoomMemberStore, RoomStore, if event.type == RoomMemberEvent.TYPE: self._store_room_member_txn(txn, event) elif event.type == FeedbackEvent.TYPE: - self._store_feedback_txn(txn,event) + self._store_feedback_txn(txn, event) # elif event.type == RoomConfigEvent.TYPE: # self._store_room_config_txn(txn, event) elif event.type == RoomNameEvent.TYPE: @@ -136,8 +137,10 @@ class DataStore(RoomMemberStore, RoomStore, self._store_join_rule(txn, event) elif event.type == RoomPowerLevelsEvent.TYPE: self._store_power_levels(txn, event) - elif event.type == RoomDefaultLevelEvent.TYPE: - self._store_default_level(txn, event) + elif event.type == RoomAddStateLevelEvent.TYPE: + self._store_add_state_level(txn, event) + elif event.type == RoomSendEventLevelEvent.TYPE: + self._store_send_event_level(txn, event) vals = { "topological_ordering": event.depth, @@ -231,7 +234,6 @@ class DataStore(RoomMemberStore, RoomStore, defer.returnValue(self.min_token) - def snapshot_room(self, room_id, user_id, state_type=None, state_key=None): """Snapshot the room for an update by a user Args: diff --git a/synapse/storage/room.py b/synapse/storage/room.py index 8946ce99f..f8aa8bd2a 100644 --- a/synapse/storage/room.py +++ b/synapse/storage/room.py @@ -174,6 +174,28 @@ class RoomStore(SQLBaseStore): else: defer.returnValue(None) + def get_add_state_level(self, room_id): + return self._get_level_from_table("room_add_state_levels", room_id) + + def get_send_event_level(self, room_id): + return self._get_level_from_table("room_send_event_levels", room_id) + + @defer.inlineCallbacks + def _get_level_from_table(self, table, room_id): + sql = ( + "SELECT level FROM %(table)s as r " + "INNER JOIN current_state_events as c " + "ON r.event_id = c.event_id " + "WHERE c.room_id = ? " + ) % {"table": table} + + rows = yield self._execute(None, sql, room_id) + + if len(rows) == 1: + defer.returnValue(rows[0][0]) + else: + defer.returnValue(None) + def _store_room_topic_txn(self, txn, event): self._simple_insert_txn( txn, @@ -196,38 +218,71 @@ class RoomStore(SQLBaseStore): } ) - def _store_join_rule(txn, event): + def _store_join_rule(self, txn, event): self._simple_insert_txn( txn, "room_join_rules", { "event_id": event.event_id, "room_id": event.room_id, - "join_rule": event.join_rule, + "join_rule": event.content["join_rule"], }, ) - def _store_power_levels(txn, event): - for user_id, level in event.content: - self._simple_insert_txn( - txn, - "room_power_levels", - { - "event_id": event.event_id, - "room_id": event.room_id, - "user_id": user_id, - "level": level - }, - ) + def _store_power_levels(self, txn, event): + for user_id, level in event.content.items(): + if user_id == "default": + self._simple_insert_txn( + txn, + "room_default_levels", + { + "event_id": event.event_id, + "room_id": event.room_id, + "level": level, + }, + ) + else: + self._simple_insert_txn( + txn, + "room_power_levels", + { + "event_id": event.event_id, + "room_id": event.room_id, + "user_id": user_id, + "level": level + }, + ) - def _store_default_level(txn, event): + def _store_default_level(self, txn, event): self._simple_insert_txn( txn, "room_default_levels", { "event_id": event.event_id, "room_id": event.room_id, - "level": level + "level": event.content["default_level"], + }, + ) + + def _store_add_state_level(self, txn, event): + self._simple_insert_txn( + txn, + "room_add_state_levels", + { + "event_id": event.event_id, + "room_id": event.room_id, + "level": event.content["level"], + }, + ) + + def _store_send_event_level(self, txn, event): + self._simple_insert_txn( + txn, + "room_send_event_levels", + { + "event_id": event.event_id, + "room_id": event.room_id, + "level": event.content["level"], }, ) diff --git a/synapse/storage/schema/im.sql b/synapse/storage/schema/im.sql index c20516b7f..447c1d29a 100644 --- a/synapse/storage/schema/im.sql +++ b/synapse/storage/schema/im.sql @@ -126,6 +126,26 @@ CREATE INDEX IF NOT EXISTS room_default_levels_event_id ON room_default_levels(e CREATE INDEX IF NOT EXISTS room_default_levels_room_id ON room_default_levels(room_id); +CREATE TABLE IF NOT EXISTS room_add_state_levels( + event_id TEXT NOT NULL, + room_id TEXT NOT NULL, + level INTEGER NOT NULL +); + +CREATE INDEX IF NOT EXISTS room_add_state_levels_event_id ON room_add_state_levels(event_id); +CREATE INDEX IF NOT EXISTS room_add_state_levels_room_id ON room_add_state_levels(room_id); + + +CREATE TABLE IF NOT EXISTS room_send_event_levels( + event_id TEXT NOT NULL, + room_id TEXT NOT NULL, + level INTEGER NOT NULL +); + +CREATE INDEX IF NOT EXISTS room_send_event_levels_event_id ON room_send_event_levels(event_id); +CREATE INDEX IF NOT EXISTS room_send_event_levels_room_id ON room_send_event_levels(room_id); + + CREATE TABLE IF NOT EXISTS room_hosts( room_id TEXT NOT NULL, host TEXT NOT NULL,