Move double puppeting login code to mautrix-go

This commit is contained in:
Tulir Asokan 2023-08-21 13:52:03 +03:00
parent fdce256a03
commit 4229f85d09
8 changed files with 45 additions and 148 deletions

View file

@ -44,9 +44,7 @@ type BridgeConfig struct {
MissedLimit int `yaml:"missed_limit"` MissedLimit int `yaml:"missed_limit"`
} `yaml:"backfill"` } `yaml:"backfill"`
DoublePuppetServerMap map[string]string `yaml:"double_puppet_server_map"` DoublePuppetConfig bridgeconfig.DoublePuppetConfig `yaml:",inline"`
DoublePuppetAllowDiscovery bool `yaml:"double_puppet_allow_discovery"`
LoginSharedSecretMap map[string]string `yaml:"login_shared_secret_map"`
PrivateChatPortalMeta string `yaml:"private_chat_portal_meta"` PrivateChatPortalMeta string `yaml:"private_chat_portal_meta"`
BridgeNotices bool `yaml:"bridge_notices"` BridgeNotices bool `yaml:"bridge_notices"`
@ -77,6 +75,10 @@ type BridgeConfig struct {
displaynameTemplate *template.Template `yaml:"-"` displaynameTemplate *template.Template `yaml:"-"`
} }
func (bc BridgeConfig) GetDoublePuppetConfig() bridgeconfig.DoublePuppetConfig {
return bc.DoublePuppetConfig
}
func (bc BridgeConfig) GetEncryptionConfig() bridgeconfig.EncryptionConfig { func (bc BridgeConfig) GetEncryptionConfig() bridgeconfig.EncryptionConfig {
return bc.Encryption return bc.Encryption
} }

View file

@ -44,6 +44,6 @@ type Config struct {
func (config *Config) CanAutoDoublePuppet(userID id.UserID) bool { func (config *Config) CanAutoDoublePuppet(userID id.UserID) bool {
_, homeserver, _ := userID.Parse() _, homeserver, _ := userID.Parse()
_, hasSecret := config.Bridge.LoginSharedSecretMap[homeserver] _, hasSecret := config.Bridge.DoublePuppetConfig.SharedSecretMap[homeserver]
return hasSecret return hasSecret
} }

View file

@ -18,22 +18,14 @@ package main
import ( import (
"context" "context"
"crypto/hmac"
"crypto/sha512"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/appservice" "maunium.net/go/mautrix/appservice"
"maunium.net/go/mautrix/bridge" "maunium.net/go/mautrix/bridge"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
) )
var (
ErrMismatchingMXID = errors.New("whoami result does not match custom mxid")
)
var _ bridge.DoublePuppet = (*User)(nil) var _ bridge.DoublePuppet = (*User)(nil)
func (user *User) SwitchCustomMXID(accessToken string, mxid id.UserID) error { func (user *User) SwitchCustomMXID(accessToken string, mxid id.UserID) error {
@ -42,14 +34,36 @@ func (user *User) SwitchCustomMXID(accessToken string, mxid id.UserID) error {
} }
user.DoublePuppetIntent = nil user.DoublePuppetIntent = nil
user.AccessToken = accessToken user.AccessToken = accessToken
err := user.startCustomMXID(false) err := user.StartCustomMXID(false)
if err != nil { if err != nil {
return err return err
} }
return nil
}
func (user *User) ClearCustomMXID() {
user.DoublePuppetIntent = nil
user.AccessToken = ""
err := user.Update(context.TODO())
if err != nil {
user.zlog.Warn().Err(err).Msg("Failed to clear access token from database")
}
}
func (user *User) StartCustomMXID(reloginOnFail bool) error {
newIntent, newAccessToken, err := user.bridge.DoublePuppet.Setup(user.MXID, user.AccessToken, reloginOnFail)
if err != nil {
user.ClearCustomMXID()
return err
}
if user.AccessToken != newAccessToken {
user.AccessToken = newAccessToken
err = user.Update(context.TODO()) err = user.Update(context.TODO())
if err != nil { if err != nil {
return fmt.Errorf("failed to save access token to database: %w", err) return fmt.Errorf("failed to save access token: %w", err)
} }
}
user.DoublePuppetIntent = newIntent
return nil return nil
} }
@ -57,121 +71,14 @@ func (user *User) CustomIntent() *appservice.IntentAPI {
return user.DoublePuppetIntent return user.DoublePuppetIntent
} }
func (user *User) loginWithSharedSecret() error { func (user *User) tryAutomaticDoublePuppeting() {
_, homeserver, _ := user.MXID.Parse() if !user.bridge.Config.CanAutoDoublePuppet(user.MXID) || user.DoublePuppetIntent != nil {
user.zlog.Debug().Msg("Logging into double puppet with shared secret") return
loginSecret := user.bridge.Config.Bridge.LoginSharedSecretMap[homeserver] }
client, err := user.bridge.newDoublePuppetClient(user.MXID, "") err := user.StartCustomMXID(true)
if err != nil { if err != nil {
return err user.zlog.Warn().Err(err).Msg("Failed to login with shared secret for double puppeting")
}
req := mautrix.ReqLogin{
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: string(user.MXID)},
DeviceID: "Google Messages Bridge",
InitialDeviceDisplayName: "Google Messages Bridge",
}
if loginSecret == "appservice" {
client.AccessToken = user.bridge.AS.Registration.AppToken
req.Type = mautrix.AuthTypeAppservice
} else { } else {
mac := hmac.New(sha512.New, []byte(loginSecret)) user.zlog.Info().Msg("Successfully automatically enabled double puppet")
mac.Write([]byte(user.MXID))
req.Password = hex.EncodeToString(mac.Sum(nil))
req.Type = mautrix.AuthTypePassword
} }
resp, err := client.Login(&req)
if err != nil {
return fmt.Errorf("failed to log in with shared secret: %w", err)
}
user.AccessToken = resp.AccessToken
err = user.Update(context.TODO())
if err != nil {
return fmt.Errorf("failed to save access token: %w", err)
}
return nil
}
func (br *GMBridge) newDoublePuppetClient(mxid id.UserID, accessToken string) (*mautrix.Client, error) {
_, homeserver, err := mxid.Parse()
if err != nil {
return nil, err
}
homeserverURL, found := br.Config.Bridge.DoublePuppetServerMap[homeserver]
if !found {
if homeserver == br.AS.HomeserverDomain {
homeserverURL = ""
} else if br.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
br.ZLog.Debug().
Str("server_name", homeserver).
Str("base_url", homeserverURL).
Str("user_id", mxid.String()).
Msg("Discovered homeserver URL to enable double puppeting for external user")
} else {
return nil, fmt.Errorf("double puppeting from %s is not allowed", homeserver)
}
}
return br.AS.NewExternalMautrixClient(mxid, accessToken, homeserverURL)
}
func (user *User) newDoublePuppetIntent() (*appservice.IntentAPI, error) {
client, err := user.bridge.newDoublePuppetClient(user.MXID, user.AccessToken)
if err != nil {
return nil, err
}
ia := user.bridge.AS.NewIntentAPI("custom")
ia.Client = client
ia.Localpart, _, _ = user.MXID.Parse()
ia.UserID = user.MXID
ia.IsCustomPuppet = true
return ia, nil
}
func (user *User) clearCustomMXID() {
user.AccessToken = ""
user.DoublePuppetIntent = nil
}
func (user *User) startCustomMXID(reloginOnFail bool) error {
if len(user.AccessToken) == 0 || user.DoublePuppetIntent != nil {
return nil
}
intent, err := user.newDoublePuppetIntent()
if err != nil {
user.clearCustomMXID()
return fmt.Errorf("failed to create double puppet intent: %w", err)
}
resp, err := intent.Whoami()
if err != nil {
if !reloginOnFail || (errors.Is(err, mautrix.MUnknownToken) && !user.tryRelogin(err)) {
user.clearCustomMXID()
return fmt.Errorf("failed to ensure double puppet token is valid: %w", err)
}
intent.AccessToken = user.AccessToken
}
if resp.UserID != user.MXID {
user.clearCustomMXID()
return ErrMismatchingMXID
}
user.DoublePuppetIntent = intent
return nil
}
func (user *User) tryRelogin(err error) bool {
if !user.bridge.Config.CanAutoDoublePuppet(user.MXID) {
return false
}
user.zlog.Debug().Err(err).Msg("Trying to relogin after error in double puppet")
err = user.loginWithSharedSecret()
if err != nil {
user.zlog.Err(err).Msg("Failed to relogin after error in double puppet")
return false
}
user.zlog.Info().Msg("Successfully relogined after error in double puppet")
return true
} }

4
go.mod
View file

@ -4,6 +4,7 @@ go 1.20
require ( require (
github.com/gabriel-vasile/mimetype v1.4.2 github.com/gabriel-vasile/mimetype v1.4.2
github.com/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.17 github.com/mattn/go-sqlite3 v1.14.17
github.com/rs/zerolog v1.30.0 github.com/rs/zerolog v1.30.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
@ -12,7 +13,7 @@ require (
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb
google.golang.org/protobuf v1.31.0 google.golang.org/protobuf v1.31.0
maunium.net/go/maulogger/v2 v2.4.1 maunium.net/go/maulogger/v2 v2.4.1
maunium.net/go/mautrix v0.16.0 maunium.net/go/mautrix v0.16.1-0.20230821105106-ac5c2c22102c
) )
require ( require (
@ -22,7 +23,6 @@ require (
github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/kr/pretty v0.3.1 // indirect github.com/kr/pretty v0.3.1 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-isatty v0.0.14 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect

4
go.sum
View file

@ -83,5 +83,5 @@ maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
maunium.net/go/maulogger/v2 v2.4.1 h1:N7zSdd0mZkB2m2JtFUsiGTQQAdP0YeFWT7YMc80yAL8= maunium.net/go/maulogger/v2 v2.4.1 h1:N7zSdd0mZkB2m2JtFUsiGTQQAdP0YeFWT7YMc80yAL8=
maunium.net/go/maulogger/v2 v2.4.1/go.mod h1:omPuYwYBILeVQobz8uO3XC8DIRuEb5rXYlQSuqrbCho= maunium.net/go/maulogger/v2 v2.4.1/go.mod h1:omPuYwYBILeVQobz8uO3XC8DIRuEb5rXYlQSuqrbCho=
maunium.net/go/mautrix v0.16.0 h1:iUqCzJE2yqBC1ddAK6eAn159My8rLb4X8g4SFtQh2Dk= maunium.net/go/mautrix v0.16.1-0.20230821105106-ac5c2c22102c h1:oRIaFbS4ds9biwJVguT+9Zu7n5zDbKQeuGklXHQxvCU=
maunium.net/go/mautrix v0.16.0/go.mod h1:XAjE9pTSGcr6vXaiNgQGiip7tddJ8FQV1a29u2QdBG4= maunium.net/go/mautrix v0.16.1-0.20230821105106-ac5c2c22102c/go.mod h1:XAjE9pTSGcr6vXaiNgQGiip7tddJ8FQV1a29u2QdBG4=

View file

@ -115,7 +115,7 @@ func (br *GMBridge) StartUsers() {
for _, loopuser := range br.GetAllUsersWithDoublePuppet() { for _, loopuser := range br.GetAllUsersWithDoublePuppet() {
go func(user *User) { go func(user *User) {
user.zlog.Debug().Msg("Starting double puppet") user.zlog.Debug().Msg("Starting double puppet")
err := user.startCustomMXID(true) err := user.StartCustomMXID(true)
if err != nil { if err != nil {
user.zlog.Err(err).Msg("Failed to start double puppet") user.zlog.Err(err).Msg("Failed to start double puppet")
} }

View file

@ -184,6 +184,8 @@ func (puppet *Puppet) SwitchCustomMXID(_ string, _ id.UserID) error {
return fmt.Errorf("puppets don't support custom MXIDs here") return fmt.Errorf("puppets don't support custom MXIDs here")
} }
func (puppet *Puppet) ClearCustomMXID() {}
func (puppet *Puppet) IntentFor(_ *Portal) *appservice.IntentAPI { func (puppet *Puppet) IntentFor(_ *Portal) *appservice.IntentAPI {
return puppet.DefaultIntent() return puppet.DefaultIntent()
} }

14
user.go
View file

@ -521,20 +521,6 @@ func (user *User) IsLoggedIn() bool {
return user.IsConnected() && user.Client.IsLoggedIn() return user.IsConnected() && user.Client.IsLoggedIn()
} }
func (user *User) tryAutomaticDoublePuppeting() {
if !user.bridge.Config.CanAutoDoublePuppet(user.MXID) || user.DoublePuppetIntent != nil {
return
}
if err := user.loginWithSharedSecret(); err != nil {
user.zlog.Warn().Err(err).Msg("Failed to login with shared secret for double puppeting")
} else if err = user.startCustomMXID(false); err != nil {
user.zlog.Warn().Err(err).Msg("Failed to start double puppet after logging in with shared secret")
} else {
user.zlog.Info().Msg("Successfully automatically enabled double puppet")
}
}
func (user *User) sendMarkdownBridgeAlert(important bool, formatString string, args ...interface{}) { func (user *User) sendMarkdownBridgeAlert(important bool, formatString string, args ...interface{}) {
if user.bridge.Config.Bridge.DisableBridgeAlerts { if user.bridge.Config.Bridge.DisableBridgeAlerts {
return return