Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Allow users to forget rooms #385

Merged
merged 6 commits into from
Nov 20, 2015
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions synapse/api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,13 @@ def check_user_was_in_room(self, room_id, user_id):
user_id, room_id
))

if membership == Membership.LEAVE:
forgot = yield self.store.did_forget(user_id, room_id)
if forgot:
raise AuthError(403, "User %s not in room %s" % (
user_id, room_id
))

defer.returnValue(member)

@defer.inlineCallbacks
Expand Down
10 changes: 9 additions & 1 deletion synapse/handlers/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,15 @@ def allowed(event, membership, visibility):

membership_event = state.get((EventTypes.Member, user_id), None)
if membership_event:
membership = membership_event.membership
was_forgotten_at_event = yield self.store.was_forgotten_at(
membership_event.user_id,
membership_event.room_id,
membership_event.event_id
)
if was_forgotten_at_event:
membership = None
else:
membership = membership_event.membership
else:
membership = None

Expand Down
3 changes: 3 additions & 0 deletions synapse/handlers/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,9 @@ def _ask_id_server_for_third_party_invite(
)
defer.returnValue((token, public_key, key_validity_url, display_name))

def forget(self, user, room_id):
self.store.forget(user.to_string(), room_id)


class RoomListHandler(BaseHandler):

Expand Down
13 changes: 10 additions & 3 deletions synapse/rest/client/v1/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ class RoomMembershipRestServlet(ClientV1RestServlet):
def register(self, http_server):
# /rooms/$roomid/[invite|join|leave]
PATTERN = ("/rooms/(?P<room_id>[^/]*)/"
"(?P<membership_action>join|invite|leave|ban|kick)")
"(?P<membership_action>join|invite|leave|ban|kick|forget)")
register_txn_path(self, PATTERN, http_server)

@defer.inlineCallbacks
Expand All @@ -458,6 +458,8 @@ def on_POST(self, request, room_id, membership_action, txn_id=None):
allow_guest=True
)

effective_membership_action = membership_action

if is_guest and membership_action not in {Membership.JOIN, Membership.LEAVE}:
raise AuthError(403, "Guest access not allowed")

Expand Down Expand Up @@ -488,11 +490,13 @@ def on_POST(self, request, room_id, membership_action, txn_id=None):
UserID.from_string(state_key)

if membership_action == "kick":
membership_action = "leave"
effective_membership_action = "leave"
elif membership_action == "forget":
effective_membership_action = "leave"

msg_handler = self.handlers.message_handler

content = {"membership": unicode(membership_action)}
content = {"membership": unicode(effective_membership_action)}
if is_guest:
content["kind"] = "guest"

Expand All @@ -509,6 +513,9 @@ def on_POST(self, request, room_id, membership_action, txn_id=None):
is_guest=is_guest,
)

if membership_action == "forget":
self.handlers.room_member_handler.forget(user, room_id)

defer.returnValue((200, {}))

def _has_3pid_invite_keys(self, content):
Expand Down
2 changes: 1 addition & 1 deletion synapse/storage/prepare_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

# Remember to update this number every time a change is made to database
# schema files, so the users will be informed on server restarts.
SCHEMA_VERSION = 25
SCHEMA_VERSION = 26

dir_path = os.path.abspath(os.path.dirname(__file__))

Expand Down
66 changes: 66 additions & 0 deletions synapse/storage/roommember.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,3 +269,69 @@ def user_rooms_intersect(self, user_id_list):
ret = len(room_id_lists.pop(0).intersection(*room_id_lists)) > 0

defer.returnValue(ret)

def forget(self, user_id, room_id):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstring

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

"""Indicate that user_id wishes to discard history for room_id."""
def f(txn):
sql = (
"UPDATE"
" room_memberships"
" SET"
" forgotten = 1"
" WHERE"
" user_id = ?"
" AND"
" room_id = ?"
)
txn.execute(sql, (user_id, room_id))
self.runInteraction("forget_membership", f)

@defer.inlineCallbacks
def did_forget(self, user_id, room_id):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstring

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

"""Returns whether user_id has elected to discard history for room_id.

Returns False if they have since re-joined."""
def f(txn):
sql = (
"SELECT"
" COUNT(*)"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this not just SELECT forgotten FROM room_memberships?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was doing this because it's possible no rooms will be returned, and consistently handling a COUNT is a little cleaner imo than having to handle different rowcounts in python.

Now that I look at the schema, though, there are no uniqueness guarantees around room_memberships rows (which should have been obvious, I guess), so I've fixed this up to also cover the forget + re-join cases properly, and added some tests for them.

Done something a little different :)

"FROM"
" room_memberships"
" WHERE"
" user_id = ?"
" AND"
" room_id = ?"
" AND"
" forgotten = 0"
)
txn.execute(sql, (user_id, room_id))
rows = txn.fetchall()
return rows[0][0]
count = yield self.runInteraction("did_forget_membership", f)
defer.returnValue(count == 0)

@defer.inlineCallbacks
def was_forgotten_at(self, user_id, room_id, event_id):
"""Returns whether user_id has elected to discard history for room_id at event_id.

event_id must be a membership event."""
def f(txn):
sql = (
"SELECT"
" COUNT(*)"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't this just SELECT forgotten FROM room_memberships?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

"FROM"
" room_memberships"
" WHERE"
" user_id = ?"
" AND"
" room_id = ?"
" AND"
" event_id = ?"
" AND"
" forgotten = 1"
)
txn.execute(sql, (user_id, room_id, event_id))
rows = txn.fetchall()
return rows[0][0]
count = yield self.runInteraction("did_forget_membership_at", f)
defer.returnValue(count == 1)
24 changes: 24 additions & 0 deletions synapse/storage/schema/delta/26/forgotten_memberships.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* Copyright 2015 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.
*/

/*
* Keeps track of what rooms users have left and don't want to be able to
* access again.
*
* If all users on this server have left a room, we can delete the room
* entirely.
*/

ALTER TABLE room_memberships ADD COLUMN forgotten INTEGER(1) DEFAULT 0;