Repository: matrix-org/synapse

Issue:
Server admins should be able to bypass restrictions on creating aliases
otherwise administration tools can't do their job


Discussion:
context: "You must be in the room to create an alias for it" - the bot isn't in the room because it's for administration, not to sit in every single room.
@turt2live Do you know if this is new? If so, it could be the change talked about in https://github.com/matrix-org/synapse/pull/6986#discussion_r384645837
It was tried on matrix.org moments before the issue was reported, so it's very much still an issue as far as I'm aware. 

The problem was the user was trying to add an alias onto a room, not delete one. The delete operation beforehand was fine.
Currently the checks for creating an alias are:

1. If `require_membership_for_aliases` is true in the config, the creator must be in the room.
2. The spam checker is queried.
3. The `alias_creation_rules` are checked from the config.
4. Services are checked to see whether they have an "exclusive lock" on an alias.

For reference, deleting an alias has the following checks:

1. If the user is the creator of the alias, they can delete it.
2. If the user is an admin, they can delete it.
3. The user must be in the room.
4. The creator has sufficient power levels to delete an alias (>= moderator).
5. Services are checked to see whether the have an "exclusive lock" on an alias.

These are similar, but not identical. Adding a check for being an admin should be straightforward. Making them more consistent should also be doable if we want to go that way.
Discussed this today and the plan is to:
* Do the straightforward work of allowing admins to create aliases.
* File a follow-up with a proposal of how to make these more consistent.

Resolving patch:
diff --git a/synapse/handlers/directory.py b/synapse/handlers/directory.py
--- a/synapse/handlers/directory.py
+++ b/synapse/handlers/directory.py
@@ -127,7 +127,11 @@ def create_association(
                     errcode=Codes.EXCLUSIVE,
                 )
         else:
-            if self.require_membership and check_membership:
+            # Server admins are not subject to the same constraints as normal
+            # users when creating an alias (e.g. being in the room).
+            is_admin = yield self.auth.is_server_admin(requester.user)
+
+            if (self.require_membership and check_membership) and not is_admin:
                 rooms_for_user = yield self.store.get_rooms_for_user(user_id)
                 if room_id not in rooms_for_user:
                     raise AuthError(


Test patch:
diff --git a/tests/handlers/test_directory.py b/tests/handlers/test_directory.py
--- a/tests/handlers/test_directory.py
+++ b/tests/handlers/test_directory.py
@@ -102,6 +102,68 @@ def test_incoming_fed_query(self):
         self.assertEquals({"room_id": "!8765asdf:test", "servers": ["test"]}, response)
 
 
+class TestCreateAlias(unittest.HomeserverTestCase):
+    servlets = [
+        synapse.rest.admin.register_servlets,
+        login.register_servlets,
+        room.register_servlets,
+        directory.register_servlets,
+    ]
+
+    def prepare(self, reactor, clock, hs):
+        self.handler = hs.get_handlers().directory_handler
+
+        # Create user
+        self.admin_user = self.register_user("admin", "pass", admin=True)
+        self.admin_user_tok = self.login("admin", "pass")
+
+        # Create a test room
+        self.room_id = self.helper.create_room_as(
+            self.admin_user, tok=self.admin_user_tok
+        )
+
+        self.test_alias = "#test:test"
+        self.room_alias = RoomAlias.from_string(self.test_alias)
+
+        # Create a test user.
+        self.test_user = self.register_user("user", "pass", admin=False)
+        self.test_user_tok = self.login("user", "pass")
+        self.helper.join(room=self.room_id, user=self.test_user, tok=self.test_user_tok)
+
+    def test_create_alias_joined_room(self):
+        """A user can create an alias for a room they're in."""
+        self.get_success(
+            self.handler.create_association(
+                create_requester(self.test_user), self.room_alias, self.room_id,
+            )
+        )
+
+    def test_create_alias_other_room(self):
+        """A user cannot create an alias for a room they're NOT in."""
+        other_room_id = self.helper.create_room_as(
+            self.admin_user, tok=self.admin_user_tok
+        )
+
+        self.get_failure(
+            self.handler.create_association(
+                create_requester(self.test_user), self.room_alias, other_room_id,
+            ),
+            synapse.api.errors.SynapseError,
+        )
+
+    def test_create_alias_admin(self):
+        """An admin can create an alias for a room they're NOT in."""
+        other_room_id = self.helper.create_room_as(
+            self.test_user, tok=self.test_user_tok
+        )
+
+        self.get_success(
+            self.handler.create_association(
+                create_requester(self.admin_user), self.room_alias, other_room_id,
+            )
+        )
+
+
 class TestDeleteAlias(unittest.HomeserverTestCase):
     servlets = [
         synapse.rest.admin.register_servlets,