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

Only count real users when checking for auto-creation of auto-join room #6004

Merged
merged 5 commits into from
Sep 9, 2019
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions changelog.d/6004.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Only count real users when checking for auto-creation of auto-join room.
12 changes: 4 additions & 8 deletions synapse/handlers/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,16 +275,12 @@ def _auto_join_rooms(self, user_id):
fake_requester = create_requester(user_id)

# try to create the room if we're the first real user on the server. Note
# that an auto-generated support user is not a real user and will never be
# that an auto-generated support or bot user is not a real user and will never be
# the user to create the room
should_auto_create_rooms = False
is_support = yield self.store.is_support_user(user_id)
# There is an edge case where the first user is the support user, then
# the room is never created, though this seems unlikely and
# recoverable from given the support user being involved in the first
# place.
if self.hs.config.autocreate_auto_join_rooms and not is_support:
count = yield self.store.count_all_users()
is_real_user = yield self.store.is_real_user(user_id)
if self.hs.config.autocreate_auto_join_rooms and is_real_user:
count = yield self.store.count_real_users()
Copy link
Member

Choose a reason for hiding this comment

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

I wonder whether it'd be easier to just check if the alias exists, rather than trying to count?

Copy link
Member Author

Choose a reason for hiding this comment

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

So the reason for the count, AFAICT, is that we only want to auto-create rooms when the first (real) user registers. I was initially going to suggest a change that rooms would always be auto-created if missing, but the problem there is that the next registering user would own any room added to the Synapse configuration.

Ideally rooms would be created on Synapse start-up to avoid this. But then the problem is who do you make the creator?

Copy link
Member

Choose a reason for hiding this comment

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

Hum, true. It sort of feels like we're using the "first real user" as a proxy for whether that user is an admin or not. I wonder if we should change it to check for the admin bit? I dunno.

Copy link
Member Author

Choose a reason for hiding this comment

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

/me throws the "I agree things could be better but since this is modifying existing functionality and things are generally broken for our use case it might be good to fix first, then refactor" -card out there ;)

Copy link
Member Author

Choose a reason for hiding this comment

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

But seriously, one possible solution would be to simplify things to not auto-create rooms, making admins who configure auto-join to pre-create the rooms. This would be simplest and avoid the problems and would probably not be too much to ask for admins to do.

Copy link
Member

Choose a reason for hiding this comment

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

Or in fact have a configuration option for a "homeserver" account, e.g. @admin:example.com which gets used to create the room

Copy link
Contributor

Choose a reason for hiding this comment

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

When this was first implemented, using the first user was considered a 'good enough' hack to get going, definitely could be improved.

should_auto_create_rooms = count == 1
for r in self.hs.config.auto_join_rooms:
logger.info("Auto-joining %s to %s", user_id, r)
Expand Down
39 changes: 39 additions & 0 deletions synapse/storage/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,19 @@ def _query_for_auth(self, txn, token):

return None

@cachedInlineCallbacks()
def is_real_user(self, user_id):
"""Determines if the user is a real user, ie does not have a 'user_type'.

Args:
user_id (str): user id to test

Returns:
Deferred[bool]: True if user 'user_type' is null or empty string
"""
res = yield self.runInteraction("is_real_user", self.is_real_user_txn, user_id)
return res

@cachedInlineCallbacks()
def is_support_user(self, user_id):
"""Determines if the user is of type UserTypes.SUPPORT
Expand All @@ -337,6 +350,16 @@ def is_support_user(self, user_id):
)
return res

def is_real_user_txn(self, txn, user_id):
res = self._simple_select_one_onecol_txn(
txn=txn,
table="users",
keyvalues={"name": user_id},
retcol="user_type",
allow_none=True,
)
return res is None

def is_support_user_txn(self, txn, user_id):
res = self._simple_select_one_onecol_txn(
txn=txn,
Expand Down Expand Up @@ -421,6 +444,22 @@ def _count_users(txn):
ret = yield self.runInteraction("count_users", _count_users)
return ret

@defer.inlineCallbacks
def count_real_users(self):
"""Counts all users without a special user_type registered on the homeserver."""

def _count_users(txn):
txn.execute(
"SELECT COUNT(*) AS users FROM users where user_type is null or user_type = ''"
Copy link
Member Author

Choose a reason for hiding this comment

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

I suppose I will change this too

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmmh, seems I broke linting again :(

)
rows = self.cursor_to_dict(txn)
if rows:
return rows[0]["users"]
return 0

ret = yield self.runInteraction("count_real_users", _count_users)
return ret

@defer.inlineCallbacks
def find_next_generated_user_id_localpart(self):
"""
Expand Down
29 changes: 27 additions & 2 deletions tests/handlers/test_register.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,18 +171,43 @@ def test_auto_create_auto_join_where_auto_create_is_false(self):
rooms = self.get_success(self.store.get_rooms_for_user(user_id))
self.assertEqual(len(rooms), 0)

def test_auto_create_auto_join_rooms_when_support_user_exists(self):
def test_auto_create_auto_join_rooms_when_user_is_not_a_real_user(self):
room_alias_str = "#room:test"
self.hs.config.auto_join_rooms = [room_alias_str]

self.store.is_support_user = Mock(return_value=True)
self.store.is_real_user = Mock(return_value=False)
user_id = self.get_success(self.handler.register_user(localpart="support"))
rooms = self.get_success(self.store.get_rooms_for_user(user_id))
self.assertEqual(len(rooms), 0)
directory_handler = self.hs.get_handlers().directory_handler
room_alias = RoomAlias.from_string(room_alias_str)
self.get_failure(directory_handler.get_association(room_alias), SynapseError)

def test_auto_create_auto_join_rooms_when_user_is_the_first_real_user(self):
room_alias_str = "#room:test"
self.hs.config.auto_join_rooms = [room_alias_str]

self.store.count_real_users = Mock(return_value=1)
self.store.is_real_user = Mock(return_value=True)
user_id = self.get_success(self.handler.register_user(localpart="real"))
rooms = self.get_success(self.store.get_rooms_for_user(user_id))
directory_handler = self.hs.get_handlers().directory_handler
room_alias = RoomAlias.from_string(room_alias_str)
room_id = self.get_success(directory_handler.get_association(room_alias))

self.assertTrue(room_id["room_id"] in rooms)
self.assertEqual(len(rooms), 1)

def test_auto_create_auto_join_rooms_when_user_is_not_the_first_real_user(self):
room_alias_str = "#room:test"
self.hs.config.auto_join_rooms = [room_alias_str]

self.store.count_real_users = Mock(return_value=2)
self.store.is_real_user = Mock(return_value=True)
user_id = self.get_success(self.handler.register_user(localpart="real"))
rooms = self.get_success(self.store.get_rooms_for_user(user_id))
self.assertEqual(len(rooms), 0)

def test_auto_create_auto_join_where_no_consent(self):
"""Test to ensure that the first user is not auto-joined to a room if
they have not given general consent.
Expand Down