Browse Source

Add option to allow double puppeting from other servers

Tulir Asokan 4 years ago
parent
commit
7c8b5d7df6

+ 8 - 1
mautrix_signal/config.py

@@ -70,7 +70,14 @@ class Config(BaseBridgeConfig):
         copy("bridge.autocreate_contact_portal")
         copy("bridge.sync_with_custom_puppets")
         copy("bridge.sync_direct_chat_list")
-        copy("bridge.login_shared_secret")
+        copy("bridge.double_puppet_server_map")
+        copy("bridge.double_puppet_allow_discovery")
+        if self["bridge.login_shared_secret"]:
+            base["bridge.login_shared_secret_map"] = {
+                base["homeserver.domain"]: self["bridge.login_shared_secret"]
+            }
+        else:
+            copy("bridge.login_shared_secret_map")
         copy("bridge.federate_rooms")
         copy("bridge.encryption.allow")
         copy("bridge.encryption.default")

+ 26 - 16
mautrix_signal/db/puppet.py

@@ -17,6 +17,8 @@ from typing import Optional, ClassVar, List, TYPE_CHECKING
 from uuid import UUID
 
 from attr import dataclass
+from yarl import URL
+import asyncpg
 
 from mausignald.types import Address
 from mautrix.types import UserID, SyncToken
@@ -39,14 +41,15 @@ class Puppet:
     custom_mxid: Optional[UserID]
     access_token: Optional[str]
     next_batch: Optional[SyncToken]
+    base_url: Optional[URL]
 
     async def insert(self) -> None:
         q = ("INSERT INTO puppet (uuid, number, name, uuid_registered, number_registered, "
-             "                    custom_mxid, access_token, next_batch) "
-             "VALUES ($1, $2, $3, $4, $5, $6, $7, $8)")
-        await self.db.execute(q, self.uuid, self.number, self.name,
-                              self.uuid_registered, self.number_registered,
-                              self.custom_mxid, self.access_token, self.next_batch)
+             "                    custom_mxid, access_token, next_batch, base_url) "
+             "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)")
+        await self.db.execute(q, self.uuid, self.number, self.name, self.uuid_registered,
+                              self.number_registered, self.custom_mxid, self.access_token,
+                              self.next_batch, str(self.base_url))
 
     async def _set_uuid(self, uuid: UUID) -> None:
         if self.uuid:
@@ -57,20 +60,27 @@ class Puppet:
     async def update(self) -> None:
         if self.uuid is None:
             q = ("UPDATE puppet SET uuid=$1, name=$3, uuid_registered=$4, number_registered=$5, "
-                 "                  custom_mxid=$6, access_token=$7, next_batch=$8 "
+                 "                  custom_mxid=$6, access_token=$7, next_batch=$8, base_url=$9 "
                  "WHERE number=$2")
         else:
             q = ("UPDATE puppet SET number=$2, name=$3, uuid_registered=$4, number_registered=$5, "
-                 "                  custom_mxid=$6, access_token=$7, next_batch=$8 "
+                 "                  custom_mxid=$6, access_token=$7, next_batch=$8, base_url=$9 "
                  "WHERE uuid=$1")
-        await self.db.execute(q, self.uuid, self.number, self.name,
-                              self.uuid_registered, self.number_registered,
-                              self.custom_mxid, self.access_token, self.next_batch)
+        await self.db.execute(q, self.uuid, self.number, self.name, self.uuid_registered,
+                              self.number_registered, self.custom_mxid, self.access_token,
+                              self.next_batch, str(self.base_url))
+
+    @classmethod
+    def _from_row(cls, row: asyncpg.Record) -> 'Puppet':
+        data = {**row}
+        base_url_str = data.pop("base_url")
+        base_url = URL(base_url_str) if base_url_str is not None else None
+        return cls(base_url=base_url, **data)
 
     @classmethod
     async def get_by_address(cls, address: Address) -> Optional['Puppet']:
         select = ("SELECT uuid, number, name, uuid_registered, "
-                  "       number_registered, custom_mxid, access_token, next_batch "
+                  "       number_registered, custom_mxid, access_token, next_batch, base_url "
                   "FROM puppet")
         if address.uuid:
             if address.number:
@@ -84,22 +94,22 @@ class Puppet:
             raise ValueError("Invalid address")
         if not row:
             return None
-        return cls(**row)
+        return cls._from_row(row)
 
     @classmethod
     async def get_by_custom_mxid(cls, mxid: UserID) -> Optional['Puppet']:
         q = ("SELECT uuid, number, name, uuid_registered, number_registered,"
-             "       custom_mxid, access_token, next_batch "
+             "       custom_mxid, access_token, next_batch, base_url "
              "FROM puppet WHERE custom_mxid=$1")
         row = await cls.db.fetchrow(q, mxid)
         if not row:
             return None
-        return cls(**row)
+        return cls._from_row(row)
 
     @classmethod
     async def all_with_custom_mxid(cls) -> List['Puppet']:
         q = ("SELECT uuid, number, name, uuid_registered, number_registered,"
-             "       custom_mxid, access_token, next_batch "
+             "       custom_mxid, access_token, next_batch, base_url "
              "FROM puppet WHERE custom_mxid IS NOT NULL")
         rows = await cls.db.fetch(q)
-        return [cls(**row) for row in rows]
+        return [cls._from_row(row) for row in rows]

+ 5 - 0
mautrix_signal/db/upgrade.py

@@ -95,3 +95,8 @@ async def upgrade_v1(conn: Connection) -> None:
 async def upgrade_v2(conn: Connection) -> None:
     await conn.execute("ALTER TABLE portal ADD COLUMN avatar_hash TEXT")
     await conn.execute("ALTER TABLE portal ADD COLUMN avatar_url TEXT")
+
+
+@upgrade_table.register(description="Add double-puppeting base_url to puppe table")
+async def upgrade_v3(conn: Connection) -> None:
+    await conn.execute("ALTER TABLE puppet ADD COLUMN base_url TEXT")

+ 8 - 1
mautrix_signal/example-config.yaml

@@ -104,12 +104,19 @@ bridge:
     # Note that updating the m.direct event is not atomic (except with mautrix-asmux)
     # and is therefore prone to race conditions.
     sync_direct_chat_list: false
+    # Allow using double puppeting from any server with a valid client .well-known file.
+    double_puppet_allow_discovery: false
+    # Servers to allow double puppeting from, even if double_puppet_allow_discovery is false.
+    double_puppet_server_map:
+        example.com: https://example.com
     # Shared secret for https://github.com/devture/matrix-synapse-shared-secret-auth
     #
     # If set, custom puppets will be enabled automatically for local users
     # instead of users having to find an access token and run `login-matrix`
     # manually.
-    login_shared_secret: null
+    # If using this for other servers than the bridge's server,
+    # you must also set the URL in the double_puppet_server_map.
+    login_shared_secret_map: {}
     # Whether or not created rooms should have federation enabled.
     # If false, created portal rooms will never be federated.
     federate_rooms: true

+ 13 - 8
mautrix_signal/puppet.py

@@ -18,6 +18,8 @@ from typing import (Optional, Dict, AsyncIterable, Awaitable, AsyncGenerator, Un
 from uuid import UUID
 import asyncio
 
+from yarl import URL
+
 from mausignald.types import Address, Contact, Profile
 from mautrix.bridge import BasePuppet
 from mautrix.appservice import IntentAPI
@@ -52,14 +54,13 @@ class Puppet(DBPuppet, BasePuppet):
     _uuid_lock: asyncio.Lock
     _update_info_lock: asyncio.Lock
 
-    def __init__(self, uuid: Optional[UUID], number: Optional[str],
-                 name: Optional[str] = None, uuid_registered: bool = False,
-                 number_registered: bool = False, custom_mxid: Optional[UserID] = None,
-                 access_token: Optional[str] = None, next_batch: Optional[SyncToken] = None
-                 ) -> None:
+    def __init__(self, uuid: Optional[UUID], number: Optional[str], name: Optional[str] = None,
+                 uuid_registered: bool = False, number_registered: bool = False,
+                 custom_mxid: Optional[UserID] = None, access_token: Optional[str] = None,
+                 next_batch: Optional[SyncToken] = None, base_url: Optional[URL] = None) -> None:
         super().__init__(uuid=uuid, number=number, name=name, uuid_registered=uuid_registered,
                          number_registered=number_registered, custom_mxid=custom_mxid,
-                         access_token=access_token, next_batch=next_batch)
+                         access_token=access_token, next_batch=next_batch, base_url=base_url)
         self.log = self.log.getChild(str(uuid) or number)
 
         self.default_mxid = self.get_mxid_from_id(self.address)
@@ -79,8 +80,12 @@ class Puppet(DBPuppet, BasePuppet):
         cls.mxid_template = SimpleTemplate(cls.config["bridge.username_template"], "userid",
                                            prefix="@", suffix=f":{cls.hs_domain}", type=str)
         cls.sync_with_custom_puppets = cls.config["bridge.sync_with_custom_puppets"]
-        secret = cls.config["bridge.login_shared_secret"]
-        cls.login_shared_secret = secret.encode("utf-8") if secret else None
+
+        cls.homeserver_url_map = {server: URL(url) for server, url
+                                  in cls.config["bridge.double_puppet_server_map"].items()}
+        cls.allow_discover_url = cls.config["bridge.double_puppet_allow_discovery"]
+        cls.login_shared_secret_map = {server: secret.encode("utf-8") for server, secret
+                                       in cls.config["bridge.login_shared_secret_map"].items()}
         cls.login_device_name = "Signal Bridge"
         return (puppet.try_start() async for puppet in cls.all_with_custom_mxid())