Amalgamate all power levels.

Remove concept of reqired power levels, something similiar can be done
using the new power level event.
This commit is contained in:
Erik Johnston 2014-11-06 16:59:13 +00:00
parent 233969bb58
commit 351c64e99e
8 changed files with 103 additions and 396 deletions

View File

@ -21,8 +21,8 @@ from synapse.api.constants import Membership, JoinRules
from synapse.api.errors import AuthError, StoreError, Codes, SynapseError from synapse.api.errors import AuthError, StoreError, Codes, SynapseError
from synapse.api.events.room import ( from synapse.api.events.room import (
RoomMemberEvent, RoomPowerLevelsEvent, RoomRedactionEvent, RoomMemberEvent, RoomPowerLevelsEvent, RoomRedactionEvent,
RoomJoinRulesEvent, RoomOpsPowerLevelsEvent, InviteJoinEvent, RoomJoinRulesEvent, InviteJoinEvent,
RoomCreateEvent, RoomSendEventLevelEvent, RoomAddStateLevelEvent, RoomCreateEvent,
) )
from synapse.util.logutils import log_function from synapse.util.logutils import log_function
@ -51,6 +51,7 @@ class Auth(object):
if event.old_state_events is None: if event.old_state_events is None:
# Oh, we don't know what the state of the room was, so we # Oh, we don't know what the state of the room was, so we
# are trusting that this is allowed (at least for now) # are trusting that this is allowed (at least for now)
logger.warn("Trusting event: %s", event.event_id)
return True return True
if hasattr(event, "outlier") and event.outlier is True: if hasattr(event, "outlier") and event.outlier is True:
@ -64,7 +65,7 @@ class Auth(object):
return True return True
if event.type == RoomMemberEvent.TYPE: if event.type == RoomMemberEvent.TYPE:
self._can_replace_state(event) self._can_send_event(event)
allowed = self.is_membership_change_allowed(event) allowed = self.is_membership_change_allowed(event)
if allowed: if allowed:
logger.debug("Allowing! %s", event) logger.debug("Allowing! %s", event)
@ -72,16 +73,7 @@ class Auth(object):
logger.debug("Denying! %s", event) logger.debug("Denying! %s", event)
return allowed return allowed
if not event.type == InviteJoinEvent.TYPE: self._can_send_event(event)
self.check_event_sender_in_room(event)
if is_state:
# TODO (erikj): This really only should be called for *new*
# state
self._can_add_state(event)
self._can_replace_state(event)
else:
self._can_send_event(event)
if event.type == RoomPowerLevelsEvent.TYPE: if event.type == RoomPowerLevelsEvent.TYPE:
self._check_power_levels(event) self._check_power_levels(event)
@ -239,21 +231,21 @@ class Auth(object):
power_level_event = event.old_state_events.get(key) power_level_event = event.old_state_events.get(key)
level = None level = None
if power_level_event: if power_level_event:
level = power_level_event.content.get(user_id) level = power_level_event.content.get("users", {}).get(user_id)
if not level: if not level:
level = power_level_event.content.get("default", 0) level = power_level_event.content.get("users_default", 0)
return level return level
def _get_ops_level_from_event_state(self, event): def _get_ops_level_from_event_state(self, event):
key = (RoomOpsPowerLevelsEvent.TYPE, "", ) key = (RoomPowerLevelsEvent.TYPE, "", )
ops_event = event.old_state_events.get(key) power_level_event = event.old_state_events.get(key)
if ops_event: if power_level_event:
return ( return (
ops_event.content.get("ban_level"), power_level_event.content.get("ban", 50),
ops_event.content.get("kick_level"), power_level_event.content.get("kick", 50),
ops_event.content.get("redact_level"), power_level_event.content.get("redact", 50),
) )
return None, None, None, return None, None, None,
@ -325,13 +317,22 @@ class Auth(object):
@log_function @log_function
def _can_send_event(self, event): def _can_send_event(self, event):
key = (RoomSendEventLevelEvent.TYPE, "", ) key = (RoomPowerLevelsEvent.TYPE, "", )
send_level_event = event.old_state_events.get(key) send_level_event = event.old_state_events.get(key)
send_level = None send_level = None
if send_level_event: if send_level_event:
send_level = send_level_event.content.get(event.user_id) send_level = send_level_event.content.get("events", {}).get(
event.type
)
if not send_level: if not send_level:
send_level = send_level_event.content.get("level", 0) if hasattr(event, "state_key"):
send_level = send_level_event.content.get(
"state_default", 50
)
else:
send_level = send_level_event.content.get(
"events_default", 0
)
if send_level: if send_level:
send_level = int(send_level) send_level = int(send_level)
@ -350,85 +351,21 @@ class Auth(object):
if user_level < send_level: if user_level < send_level:
raise AuthError( raise AuthError(
403, "You don't have permission to post to the room" 403, "You don't have permission to post that to the room"
) )
return True return True
def _can_add_state(self, event):
key = (RoomAddStateLevelEvent.TYPE, "", )
add_level_event = event.old_state_events.get(key)
add_level = None
if add_level_event:
add_level = add_level_event.content.get(event.user_id)
if not add_level:
add_level = add_level_event.content.get("level", 0)
if add_level:
add_level = int(add_level)
else:
add_level = 0
user_level = self._get_power_level_from_event_state(
event,
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"
)
return True
def _can_replace_state(self, event):
user_level = self._get_power_level_from_event_state(
event,
event.user_id,
)
if user_level:
user_level = int(user_level)
else:
user_level = 0
logger.debug(
"Checking power level for %s, %s", event.user_id, user_level
)
key = (event.type, event.state_key, )
current_state = event.old_state_events.get(key)
if current_state and hasattr(current_state, "required_power_level"):
req = current_state.required_power_level
logger.debug("Checked power level for %s, %s", event.user_id, req)
if user_level < req:
raise AuthError(
403,
"You don't have permission to change that state"
)
def _check_redaction(self, event): def _check_redaction(self, event):
user_level = self._get_power_level_from_event_state( user_level = self._get_power_level_from_event_state(
event, event,
event.user_id, event.user_id,
) )
if user_level:
user_level = int(user_level)
else:
user_level = 0
_, _, redact_level = self._get_ops_level_from_event_state( _, _, redact_level = self._get_ops_level_from_event_state(
event event
) )
if not redact_level:
redact_level = 50
if user_level < redact_level: if user_level < redact_level:
raise AuthError( raise AuthError(
403, 403,
@ -436,14 +373,9 @@ class Auth(object):
) )
def _check_power_levels(self, event): def _check_power_levels(self, event):
for k, v in event.content.items(): user_list = event.content.get("users", {})
if k == "default": # Validate users
continue for k, v in user_list.items():
# FIXME (erikj): We don't want hsob_Ts in content.
if k == "hsob_ts":
continue
try: try:
self.hs.parse_userid(k) self.hs.parse_userid(k)
except: except:
@ -459,72 +391,63 @@ class Auth(object):
if not current_state: if not current_state:
return return
else:
current_state = current_state[0]
user_level = self._get_power_level_from_event_state( user_level = self._get_power_level_from_event_state(
event, event,
event.user_id, event.user_id,
) )
if user_level: # Check other levels:
user_level = int(user_level) levels_to_check = [
else: ("users_default", []),
user_level = 0 ("events_default", []),
("ban", []),
("redact", []),
("kick", []),
]
old_list = current_state.content old_list = current_state.content.get("users")
for user in set(old_list.keys() + user_list.keys()):
levels_to_check.append(
(user, ["users"])
)
# FIXME (erikj) old_list = current_state.content.get("events")
old_people = {k: v for k, v in old_list.items() if k.startswith("@")} new_list = event.content.get("events")
new_people = { for ev_id in set(old_list.keys() + new_list.keys()):
k: v for k, v in event.content.items() levels_to_check.append(
if k.startswith("@") (ev_id, ["events"])
} )
removed = set(old_people.keys()) - set(new_people.keys()) old_state = current_state.content
added = set(new_people.keys()) - set(old_people.keys()) new_state = event.content
same = set(old_people.keys()) & set(new_people.keys())
for r in removed: for level_to_check, dir in levels_to_check:
if int(old_list[r]) > user_level: old_loc = old_state
raise AuthError( for d in dir:
403, old_loc = old_loc.get(d, {})
"You don't have permission to remove user: %s" % (r, )
)
for n in added: new_loc = new_state
if int(event.content[n]) > user_level: for d in dir:
new_loc = new_loc.get(d, {})
if level_to_check in old_loc:
old_level = int(old_loc[level_to_check])
else:
old_level = None
if level_to_check in new_loc:
new_level = int(new_loc[level_to_check])
else:
new_level = None
if new_level is not None and old_level is not None:
if new_level == old_level:
continue
if old_level > user_level or new_level > user_level:
raise AuthError( raise AuthError(
403, 403,
"You don't have permission to add ops level greater " "You don't have permission to add ops level greater "
"than your own" "than your own"
) )
for s in same:
if int(event.content[s]) != int(old_list[s]):
if int(event.content[s]) > user_level:
raise AuthError(
403,
"You don't have permission to add ops level greater "
"than your own"
)
if "default" in old_list:
old_default = int(old_list["default"])
if old_default > user_level:
raise AuthError(
403,
"You don't have permission to add ops level greater than "
"your own"
)
if "default" in event.content:
new_default = int(event.content["default"])
if new_default > user_level:
raise AuthError(
403,
"You don't have permission to add ops level greater "
"than your own"
)

View File

@ -56,12 +56,12 @@ class SynapseEvent(JsonEncodedObject):
"user_id", # sender/initiator "user_id", # sender/initiator
"content", # HTTP body, JSON "content", # HTTP body, JSON
"state_key", "state_key",
"required_power_level",
"age_ts", "age_ts",
"prev_content", "prev_content",
"replaces_state", "replaces_state",
"redacted_because", "redacted_because",
"origin_server_ts", "origin_server_ts",
"auth_chains",
] ]
internal_keys = [ internal_keys = [
@ -70,7 +70,6 @@ class SynapseEvent(JsonEncodedObject):
"destinations", "destinations",
"origin", "origin",
"outlier", "outlier",
"power_level",
"redacted", "redacted",
"prev_events", "prev_events",
"hashes", "hashes",

View File

@ -16,8 +16,8 @@
from synapse.api.events.room import ( from synapse.api.events.room import (
RoomTopicEvent, MessageEvent, RoomMemberEvent, FeedbackEvent, RoomTopicEvent, MessageEvent, RoomMemberEvent, FeedbackEvent,
InviteJoinEvent, RoomConfigEvent, RoomNameEvent, GenericEvent, InviteJoinEvent, RoomConfigEvent, RoomNameEvent, GenericEvent,
RoomPowerLevelsEvent, RoomJoinRulesEvent, RoomOpsPowerLevelsEvent, RoomPowerLevelsEvent, RoomJoinRulesEvent,
RoomCreateEvent, RoomAddStateLevelEvent, RoomSendEventLevelEvent, RoomCreateEvent,
RoomRedactionEvent, RoomRedactionEvent,
) )
@ -39,9 +39,6 @@ class EventFactory(object):
RoomPowerLevelsEvent, RoomPowerLevelsEvent,
RoomJoinRulesEvent, RoomJoinRulesEvent,
RoomCreateEvent, RoomCreateEvent,
RoomAddStateLevelEvent,
RoomSendEventLevelEvent,
RoomOpsPowerLevelsEvent,
RoomRedactionEvent, RoomRedactionEvent,
] ]

View File

@ -153,28 +153,6 @@ class RoomPowerLevelsEvent(SynapseStateEvent):
def get_content_template(self): def get_content_template(self):
return {} return {}
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 {}
class RoomOpsPowerLevelsEvent(SynapseStateEvent):
TYPE = "m.room.ops_levels"
def get_content_template(self):
return {}
class RoomAliasesEvent(SynapseStateEvent): class RoomAliasesEvent(SynapseStateEvent):
TYPE = "m.room.aliases" TYPE = "m.room.aliases"

View File

@ -15,7 +15,6 @@
from .room import ( from .room import (
RoomMemberEvent, RoomJoinRulesEvent, RoomPowerLevelsEvent, RoomMemberEvent, RoomJoinRulesEvent, RoomPowerLevelsEvent,
RoomAddStateLevelEvent, RoomSendEventLevelEvent, RoomOpsPowerLevelsEvent,
RoomAliasesEvent, RoomCreateEvent, RoomAliasesEvent, RoomCreateEvent,
) )
@ -52,17 +51,17 @@ def _prune_event_or_pdu(event_type, event):
elif event_type == RoomJoinRulesEvent.TYPE: elif event_type == RoomJoinRulesEvent.TYPE:
add_fields("join_rule") add_fields("join_rule")
elif event_type == RoomPowerLevelsEvent.TYPE: elif event_type == RoomPowerLevelsEvent.TYPE:
# TODO: Actually check these are valid user_ids etc. add_fields(
add_fields("default") "users",
for k, v in event.content.items(): "users_default",
if k.startswith("@") and isinstance(v, (int, long)): "events",
new_content[k] = v "events_default",
elif event_type == RoomAddStateLevelEvent.TYPE: "events_default",
add_fields("level") "state_default",
elif event_type == RoomSendEventLevelEvent.TYPE: "ban",
add_fields("level") "kick",
elif event_type == RoomOpsPowerLevelsEvent.TYPE: "redact",
add_fields("kick_level", "ban_level", "redact_level") )
elif event_type == RoomAliasesEvent.TYPE: elif event_type == RoomAliasesEvent.TYPE:
add_fields("aliases") add_fields("aliases")

View File

@ -21,8 +21,7 @@ from synapse.api.constants import Membership, JoinRules
from synapse.api.errors import StoreError, SynapseError from synapse.api.errors import StoreError, SynapseError
from synapse.api.events.room import ( from synapse.api.events.room import (
RoomMemberEvent, RoomCreateEvent, RoomPowerLevelsEvent, RoomMemberEvent, RoomCreateEvent, RoomPowerLevelsEvent,
RoomJoinRulesEvent, RoomAddStateLevelEvent, RoomTopicEvent, RoomTopicEvent, RoomNameEvent, RoomJoinRulesEvent,
RoomSendEventLevelEvent, RoomOpsPowerLevelsEvent, RoomNameEvent,
) )
from synapse.util import stringutils from synapse.util import stringutils
from ._base import BaseHandler from ._base import BaseHandler
@ -139,7 +138,6 @@ class RoomCreationHandler(BaseHandler):
etype=RoomNameEvent.TYPE, etype=RoomNameEvent.TYPE,
room_id=room_id, room_id=room_id,
user_id=user_id, user_id=user_id,
required_power_level=50,
content={"name": name}, content={"name": name},
) )
@ -151,7 +149,6 @@ class RoomCreationHandler(BaseHandler):
etype=RoomTopicEvent.TYPE, etype=RoomTopicEvent.TYPE,
room_id=room_id, room_id=room_id,
user_id=user_id, user_id=user_id,
required_power_level=50,
content={"topic": topic}, content={"topic": topic},
) )
@ -196,7 +193,6 @@ class RoomCreationHandler(BaseHandler):
event_keys = { event_keys = {
"room_id": room_id, "room_id": room_id,
"user_id": creator.to_string(), "user_id": creator.to_string(),
"required_power_level": 100,
} }
def create(etype, **content): def create(etype, **content):
@ -213,7 +209,21 @@ class RoomCreationHandler(BaseHandler):
power_levels_event = self.event_factory.create_event( power_levels_event = self.event_factory.create_event(
etype=RoomPowerLevelsEvent.TYPE, etype=RoomPowerLevelsEvent.TYPE,
content={creator.to_string(): 100, "default": 0}, content={
"users": {
creator.to_string(): 100,
},
"users_default": 0,
"events": {
RoomNameEvent.TYPE: 100,
RoomPowerLevelsEvent.TYPE: 100,
},
"events_default": 0,
"state_default": 50,
"ban": 50,
"kick": 50,
"redact": 50
},
**event_keys **event_keys
) )
@ -223,30 +233,10 @@ class RoomCreationHandler(BaseHandler):
join_rule=join_rule, join_rule=join_rule,
) )
add_state_event = create(
etype=RoomAddStateLevelEvent.TYPE,
level=100,
)
send_event = create(
etype=RoomSendEventLevelEvent.TYPE,
level=0,
)
ops = create(
etype=RoomOpsPowerLevelsEvent.TYPE,
ban_level=50,
kick_level=50,
redact_level=50,
)
return [ return [
creation_event, creation_event,
power_levels_event, power_levels_event,
join_rules_event, join_rules_event,
add_state_event,
send_event,
ops,
] ]
@ -388,16 +378,6 @@ class RoomMemberHandler(BaseHandler):
else: else:
# This is not a JOIN, so we can handle it normally. # This is not a JOIN, so we can handle it normally.
# If we're banning someone, set a req power level
if event.membership == Membership.BAN:
if not hasattr(event, "required_power_level") or event.required_power_level is None:
# Add some default required_power_level
user_level = yield self.store.get_power_level(
event.room_id,
event.user_id,
)
event.required_power_level = user_level
if prev_state and prev_state.membership == event.membership: if prev_state and prev_state.membership == event.membership:
# double same action, treat this event as a NOOP. # double same action, treat this event as a NOOP.
defer.returnValue({}) defer.returnValue({})

View File

@ -17,13 +17,9 @@ from twisted.internet import defer
from synapse.api.events.room import ( from synapse.api.events.room import (
RoomMemberEvent, RoomTopicEvent, FeedbackEvent, RoomMemberEvent, RoomTopicEvent, FeedbackEvent,
# RoomConfigEvent,
RoomNameEvent, RoomNameEvent,
RoomJoinRulesEvent, RoomJoinRulesEvent,
RoomPowerLevelsEvent, RoomPowerLevelsEvent,
RoomAddStateLevelEvent,
RoomSendEventLevelEvent,
RoomOpsPowerLevelsEvent,
RoomRedactionEvent, RoomRedactionEvent,
) )
@ -166,14 +162,6 @@ class DataStore(RoomMemberStore, RoomStore,
self._store_room_topic_txn(txn, event) self._store_room_topic_txn(txn, event)
elif event.type == RoomJoinRulesEvent.TYPE: elif event.type == RoomJoinRulesEvent.TYPE:
self._store_join_rule(txn, event) self._store_join_rule(txn, event)
elif event.type == RoomPowerLevelsEvent.TYPE:
self._store_power_levels(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)
elif event.type == RoomOpsPowerLevelsEvent.TYPE:
self._store_ops_level(txn, event)
elif event.type == RoomRedactionEvent.TYPE: elif event.type == RoomRedactionEvent.TYPE:
self._store_redaction(txn, event) self._store_redaction(txn, event)

View File

@ -148,85 +148,6 @@ class RoomStore(SQLBaseStore):
else: else:
defer.returnValue(None) defer.returnValue(None)
def get_power_level(self, room_id, user_id):
return self.runInteraction(
"get_power_level",
self._get_power_level,
room_id, user_id,
)
def _get_power_level(self, txn, room_id, user_id):
sql = (
"SELECT level FROM room_power_levels as r "
"INNER JOIN current_state_events as c "
"ON r.event_id = c.event_id "
"WHERE c.room_id = ? AND r.user_id = ? "
)
rows = txn.execute(sql, (room_id, user_id,)).fetchall()
if len(rows) == 1:
return rows[0][0]
sql = (
"SELECT level FROM room_default_levels as r "
"INNER JOIN current_state_events as c "
"ON r.event_id = c.event_id "
"WHERE c.room_id = ? "
)
rows = txn.execute(sql, (room_id,)).fetchall()
if len(rows) == 1:
return rows[0][0]
else:
return None
def get_ops_levels(self, room_id):
return self.runInteraction(
"get_ops_levels",
self._get_ops_levels,
room_id,
)
def _get_ops_levels(self, txn, room_id):
sql = (
"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 "
"WHERE c.room_id = ? "
)
rows = txn.execute(sql, (room_id,)).fetchall()
if len(rows) == 1:
return OpsLevel(rows[0][0], rows[0][1], rows[0][2])
else:
return OpsLevel(None, 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): def _store_room_topic_txn(self, txn, event):
self._simple_insert_txn( self._simple_insert_txn(
txn, txn,
@ -260,84 +181,6 @@ class RoomStore(SQLBaseStore):
}, },
) )
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(self, txn, event):
self._simple_insert_txn(
txn,
"room_default_levels",
{
"event_id": event.event_id,
"room_id": event.room_id,
"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"],
},
)
def _store_ops_level(self, txn, event):
content = {
"event_id": event.event_id,
"room_id": event.room_id,
}
if "kick_level" in event.content:
content["kick_level"] = event.content["kick_level"]
if "ban_level" in event.content:
content["ban_level"] = event.content["ban_level"]
if "redact_level" in event.content:
content["redact_level"] = event.content["redact_level"]
self._simple_insert_txn(
txn,
"room_ops_levels",
content,
)
class RoomsTable(Table): class RoomsTable(Table):
table_name = "rooms" table_name = "rooms"