Browse Source

Add support for automatic double puppeting from other servers

Tulir Asokan 3 years ago
parent
commit
de9977b7d2
5 changed files with 70 additions and 37 deletions
  1. 9 7
      config/bridge.go
  2. 13 9
      config/config.go
  3. 39 15
      custompuppet.go
  4. 8 5
      example-config.yaml
  5. 1 1
      user.go

+ 9 - 7
config/bridge.go

@@ -46,11 +46,15 @@ type BridgeConfig struct {
 	UserAvatarSync    bool `yaml:"user_avatar_sync"`
 	BridgeMatrixLeave bool `yaml:"bridge_matrix_leave"`
 
-	SyncWithCustomPuppets bool   `yaml:"sync_with_custom_puppets"`
-	SyncDirectChatList    bool   `yaml:"sync_direct_chat_list"`
-	DefaultBridgeReceipts bool   `yaml:"default_bridge_receipts"`
-	DefaultBridgePresence bool   `yaml:"default_bridge_presence"`
-	LoginSharedSecret     string `yaml:"login_shared_secret"`
+	SyncWithCustomPuppets bool `yaml:"sync_with_custom_puppets"`
+	SyncDirectChatList    bool `yaml:"sync_direct_chat_list"`
+	DefaultBridgeReceipts bool `yaml:"default_bridge_receipts"`
+	DefaultBridgePresence bool `yaml:"default_bridge_presence"`
+
+	DoublePuppetServerMap      map[string]string `yaml:"double_puppet_server_map"`
+	DoublePuppetAllowDiscovery bool              `yaml:"double_puppet_allow_discovery"`
+	LoginSharedSecretMap       map[string]string `yaml:"login_shared_secret_map"`
+	LegacyLoginSharedSecret    string            `yaml:"login_shared_secret"`
 
 	PrivateChatPortalMeta bool   `yaml:"private_chat_portal_meta"`
 	BridgeNotices         bool   `yaml:"bridge_notices"`
@@ -92,8 +96,6 @@ type BridgeConfig struct {
 
 	Relay RelaybotConfig `yaml:"relay"`
 
-	DoublePuppetServerMap map[string]string `yaml:"double_puppet_server_map"`
-
 	usernameTemplate    *template.Template `yaml:"-"`
 	displaynameTemplate *template.Template `yaml:"-"`
 }

+ 13 - 9
config/config.go

@@ -21,6 +21,7 @@ import (
 	"os"
 
 	"gopkg.in/yaml.v2"
+
 	"maunium.net/go/mautrix/id"
 
 	"maunium.net/go/mautrix/appservice"
@@ -80,15 +81,10 @@ type Config struct {
 	Logging appservice.LogConfig `yaml:"logging"`
 }
 
-func (config *Config) CanDoublePuppet(userID id.UserID) bool {
-	if len(config.Bridge.LoginSharedSecret) == 0 {
-		// Automatic login not enabled
-		return false
-	} else if _, homeserver, _ := userID.Parse(); homeserver != config.Homeserver.Domain {
-		// user is on another homeserver
-		return false
-	}
-	return true
+func (config *Config) CanAutoDoublePuppet(userID id.UserID) bool {
+	_, homeserver, _ := userID.Parse()
+	_, hasSecret := config.Bridge.LoginSharedSecretMap[homeserver]
+	return hasSecret
 }
 
 func Load(path string) (*Config, error) {
@@ -103,6 +99,14 @@ func Load(path string) (*Config, error) {
 		return nil, fmt.Errorf("failed to unmarshal example config: %w", err)
 	}
 	err = yaml.Unmarshal(data, config)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(config.Bridge.LegacyLoginSharedSecret) > 0 {
+		config.Bridge.LoginSharedSecretMap[config.Homeserver.Domain] = config.Bridge.LegacyLoginSharedSecret
+	}
+
 	return config, err
 }
 

+ 39 - 15
custompuppet.go

@@ -21,6 +21,7 @@ import (
 	"crypto/sha512"
 	"encoding/hex"
 	"errors"
+	"fmt"
 	"time"
 
 	"go.mau.fi/whatsmeow/types"
@@ -64,10 +65,15 @@ func (puppet *Puppet) SwitchCustomMXID(accessToken string, mxid id.UserID) error
 }
 
 func (puppet *Puppet) loginWithSharedSecret(mxid id.UserID) (string, error) {
+	_, homeserver, _ := mxid.Parse()
 	puppet.log.Debugfln("Logging into %s with shared secret", mxid)
-	mac := hmac.New(sha512.New, []byte(puppet.bridge.Config.Bridge.LoginSharedSecret))
+	mac := hmac.New(sha512.New, []byte(puppet.bridge.Config.Bridge.LoginSharedSecretMap[homeserver]))
 	mac.Write([]byte(mxid))
-	resp, err := puppet.bridge.AS.BotClient().Login(&mautrix.ReqLogin{
+	client, err := puppet.bridge.newDoublePuppetClient(mxid, "")
+	if err != nil {
+		return "", fmt.Errorf("failed to create mautrix client to log in: %v", err)
+	}
+	resp, err := client.Login(&mautrix.ReqLogin{
 		Type:                     mautrix.AuthTypePassword,
 		Identifier:               mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: string(mxid)},
 		Password:                 hex.EncodeToString(mac.Sum(nil)),
@@ -80,26 +86,44 @@ func (puppet *Puppet) loginWithSharedSecret(mxid id.UserID) (string, error) {
 	return resp.AccessToken, nil
 }
 
-func (puppet *Puppet) newCustomIntent() (*appservice.IntentAPI, error) {
-	if len(puppet.CustomMXID) == 0 {
-		return nil, ErrNoCustomMXID
-	}
-	_, homeserver, err := puppet.CustomMXID.Parse()
+func (bridge *Bridge) newDoublePuppetClient(mxid id.UserID, accessToken string) (*mautrix.Client, error) {
+	_, homeserver, err := mxid.Parse()
 	if err != nil {
 		return nil, err
 	}
-	homeserverUrl, found := puppet.bridge.Config.Bridge.DoublePuppetServerMap[homeserver]
+	homeserverURL, found := bridge.Config.Bridge.DoublePuppetServerMap[homeserver]
 	if !found {
-		puppet.log.Debugfln("Homeserver not found in double puppet server map. Using local homeserver")
-		homeserverUrl = puppet.bridge.AS.HomeserverURL
+		if homeserver == bridge.AS.HomeserverDomain {
+			homeserverURL = bridge.AS.HomeserverURL
+		} else if bridge.Config.Bridge.DoublePuppetAllowDiscovery {
+			resp, err := mautrix.DiscoverClientAPI(homeserver)
+			if err != nil {
+				return nil, fmt.Errorf("failed to find homeserver URL for %s: %v", homeserver, err)
+			}
+			homeserverURL = resp.Homeserver.BaseURL
+			bridge.Log.Debugfln("Discovered URL %s for %s to enable double puppeting for %s", homeserverURL, homeserver, mxid)
+		} else {
+			return nil, fmt.Errorf("double puppeting from %s is not allowed", homeserver)
+		}
+	}
+	client, err := mautrix.NewClient(homeserverURL, mxid, accessToken)
+	if err != nil {
+		return nil, err
+	}
+	client.Logger = bridge.AS.Log.Sub(mxid.String())
+	client.Client = bridge.AS.HTTPClient
+	client.DefaultHTTPRetries = bridge.AS.DefaultHTTPRetries
+	return client, nil
+}
+
+func (puppet *Puppet) newCustomIntent() (*appservice.IntentAPI, error) {
+	if len(puppet.CustomMXID) == 0 {
+		return nil, ErrNoCustomMXID
 	}
-	client, err := mautrix.NewClient(homeserverUrl, puppet.CustomMXID, puppet.AccessToken)
+	client, err := puppet.bridge.newDoublePuppetClient(puppet.CustomMXID, puppet.AccessToken)
 	if err != nil {
 		return nil, err
 	}
-	client.Logger = puppet.bridge.AS.Log.Sub(string(puppet.CustomMXID))
-	client.Client = puppet.bridge.AS.HTTPClient
-	client.DefaultHTTPRetries = puppet.bridge.AS.DefaultHTTPRetries
 	client.Syncer = puppet
 	client.Store = puppet
 
@@ -264,7 +288,7 @@ func (puppet *Puppet) handleTypingEvent(portal *Portal, evt *event.Event) {
 }
 
 func (puppet *Puppet) tryRelogin(cause error, action string) bool {
-	if !puppet.bridge.Config.CanDoublePuppet(puppet.CustomMXID) {
+	if !puppet.bridge.Config.CanAutoDoublePuppet(puppet.CustomMXID) {
 		return false
 	}
 	puppet.log.Debugfln("Trying to relogin after '%v' while %s", cause, action)

+ 8 - 5
example-config.yaml

@@ -124,12 +124,18 @@ bridge:
     # Existing users won't be affected when these are changed.
     default_bridge_receipts: true
     default_bridge_presence: true
-    # Shared secret for https://github.com/devture/matrix-synapse-shared-secret-auth
+    # Servers to always allow double puppeting from
+    double_puppet_server_map:
+        example.com: https://example.com
+    # Allow using double puppeting from any server with a valid client .well-known file.
+    double_puppet_allow_discovery: false
+    # Shared secrets for https://github.com/devture/matrix-synapse-shared-secret-auth
     #
     # If set, double puppeting 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
+    login_shared_secret_map:
+        example.com: foobar
 
     # Should the bridge explicitly set the avatar and room name for private chat portal rooms?
     private_chat_portal_meta: false
@@ -216,9 +222,6 @@ bridge:
         "example.com": user
         "@admin:example.com": admin
 
-    double_puppet_server_map: 
-      "matrix.org": https://matrix.org
-
     relay:
         # Whether relay mode should be allowed. If allowed, `!wa set-relay` can be used to turn any
         # authenticated user into a relaybot for that chat.

+ 1 - 1
user.go

@@ -299,7 +299,7 @@ func (user *User) IsLoggedIn() bool {
 }
 
 func (user *User) tryAutomaticDoublePuppeting() {
-	if !user.bridge.Config.CanDoublePuppet(user.MXID) {
+	if !user.bridge.Config.CanAutoDoublePuppet(user.MXID) {
 		return
 	}
 	user.log.Debugln("Checking if double puppeting needs to be enabled")