Add support for automatic double puppeting from other servers

This commit is contained in:
Tulir Asokan 2021-11-06 13:57:35 +02:00
parent 348b7bfe7b
commit de9977b7d2
5 changed files with 71 additions and 38 deletions

View file

@ -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:"-"`
}

View file

@ -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
}

View file

@ -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 (bridge *Bridge) newDoublePuppetClient(mxid id.UserID, accessToken string) (*mautrix.Client, error) {
_, homeserver, err := mxid.Parse()
if err != nil {
return nil, err
}
homeserverURL, found := bridge.Config.Bridge.DoublePuppetServerMap[homeserver]
if !found {
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
}
_, homeserver, err := puppet.CustomMXID.Parse()
client, err := puppet.bridge.newDoublePuppetClient(puppet.CustomMXID, puppet.AccessToken)
if err != nil {
return nil, err
}
homeserverUrl, found := puppet.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
}
client, err := mautrix.NewClient(homeserverUrl, 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)

View file

@ -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.

View file

@ -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")