update double puppet related logic, fix the issue user will rejion matrix room even if user has logout skype(bridge)

This commit is contained in:
zhaoYangguang 2021-12-09 17:27:06 +08:00
parent 69f4670ed0
commit 362e918cb6
10 changed files with 159 additions and 106 deletions

View File

@ -262,6 +262,13 @@ func (handler *CommandHandler) CommandLogout(ce *CommandEvent) {
Contacts: make(map[string]skype.Contact),
Chats: make(map[string]skype.Conversation),
}
puppet := handler.bridge.GetPuppetByJID(ce.User.JID)
if puppet.CustomMXID != "" {
err := puppet.SwitchCustomMXID("", "")
if err != nil {
ce.User.log.Warnln("Failed to logout-matrix while logging out of WhatsApp:", err)
}
}
ce.Reply("Logged out successfully.")
if ce.User.Conn.Refresh != nil {
ce.User.Conn.Refresh <- -1

View File

@ -2,11 +2,12 @@ package config
import (
"bytes"
skype "github.com/kelaresg/go-skypeapi"
"strconv"
"strings"
"text/template"
skype "github.com/kelaresg/go-skypeapi"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
@ -41,11 +42,14 @@ type BridgeConfig struct {
SyncChatMaxAge uint64 `yaml:"sync_max_chat_age"`
SyncWithCustomPuppets bool `yaml:"sync_with_custom_puppets"`
LoginSharedSecret string `yaml:"login_shared_secret"`
InviteOwnPuppetForBackfilling bool `yaml:"invite_own_puppet_for_backfilling"`
PrivateChatPortalMeta bool `yaml:"private_chat_portal_meta"`
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"`
WhatsappThumbnail bool `yaml:"whatsapp_thumbnail"`
AllowUserInvite bool `yaml:"allow_user_invite"`
@ -99,7 +103,6 @@ func (bc *BridgeConfig) setDefaults() {
bc.SyncChatMaxAge = 259200
bc.SyncWithCustomPuppets = true
bc.LoginSharedSecret = ""
bc.InviteOwnPuppetForBackfilling = true
bc.PrivateChatPortalMeta = false

View File

@ -18,6 +18,8 @@ package config
import (
"io/ioutil"
"maunium.net/go/mautrix/id"
"maunium.net/go/mautrix/patch"
"gopkg.in/yaml.v2"
@ -68,6 +70,12 @@ type Config struct {
Logging appservice.LogConfig `yaml:"logging"`
}
func (config *Config) CanAutoDoublePuppet(userID id.UserID) bool {
_, homeserver, _ := userID.Parse()
_, hasSecret := config.Bridge.LoginSharedSecretMap[homeserver]
return hasSecret
}
func (config *Config) setDefaults() {
config.AppService.Database.MaxOpenConns = 20
config.AppService.Database.MaxIdleConns = 2

View File

@ -11,18 +11,18 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//go:build cgo && !nocrypto
// +build cgo,!nocrypto
package main
import (
"crypto/hmac"
"crypto/sha512"
"encoding/hex"
"fmt"
"runtime/debug"
"time"
"github.com/pkg/errors"
"github.com/lib/pq"
"maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix"
@ -48,13 +48,14 @@ type CryptoHelper struct {
baseLog maulogger.Logger
}
func init() {
crypto.PostgresArrayWrapper = pq.Array
}
func NewCryptoHelper(bridge *Bridge) Crypto {
if !bridge.Config.Bridge.Encryption.Allow {
bridge.Log.Debugln("Bridge built with end-to-bridge encryption, but disabled in config")
return nil
} else if bridge.Config.Bridge.LoginSharedSecret == "" {
bridge.Log.Warnln("End-to-bridge encryption enabled, but login_shared_secret not set")
return nil
}
baseLog := bridge.Log.Sub("Crypto")
return &CryptoHelper{
@ -101,7 +102,8 @@ func (helper *CryptoHelper) allowKeyShare(device *crypto.DeviceIdentity, info ev
return &crypto.KeyShareRejection{Code: event.RoomKeyWithheldUnavailable, Reason: "Requested room is not a portal room"}
}
user := helper.bridge.GetUserByMXID(device.UserID)
if !user.IsInPortal(portal.Key) {
// FIXME reimplement IsInPortal
if !user.Admin /*&& !user.IsInPortal(portal.Key)*/ {
helper.log.Debugfln("Rejecting key request for %s from %s/%s: user is not in portal", info.SessionID, device.UserID, device.DeviceID)
return &crypto.KeyShareRejection{Code: event.RoomKeyWithheldUnauthorized, Reason: "You're not in that portal"}
}
@ -128,14 +130,15 @@ func (helper *CryptoHelper) loginBot() (*mautrix.Client, error) {
if err != nil {
return nil, fmt.Errorf("failed to get supported login flows: %w", err)
}
if !flows.HasFlow(mautrix.AuthTypeHalfyAppservice) {
flow := flows.FirstFlowOfType(mautrix.AuthTypeAppservice, mautrix.AuthTypeHalfyAppservice)
if flow == nil {
return nil, fmt.Errorf("homeserver does not support appservice login")
}
// We set the API token to the AS token here to authenticate the appservice login
// It'll get overridden after the login
client.AccessToken = helper.bridge.AS.Registration.AppToken
resp, err := client.Login(&mautrix.ReqLogin{
Type: mautrix.AuthTypeHalfyAppservice,
Type: flow.Type,
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: string(helper.bridge.AS.BotMXID())},
DeviceID: deviceID,
InitialDeviceDisplayName: "Skype Bridge",
@ -144,37 +147,7 @@ func (helper *CryptoHelper) loginBot() (*mautrix.Client, error) {
if err != nil {
return nil, fmt.Errorf("failed to log in as bridge bot: %w", err)
}
if len(deviceID) == 0 {
helper.store.DeviceID = resp.DeviceID
}
return client, nil
}
func (helper *CryptoHelper) loginBotOld() (*mautrix.Client, error) {
deviceID := helper.store.FindDeviceID()
if len(deviceID) > 0 {
helper.log.Debugln("Found existing device ID for bot in database:", deviceID)
}
mac := hmac.New(sha512.New, []byte(helper.bridge.Config.Bridge.LoginSharedSecret))
mac.Write([]byte(helper.bridge.AS.BotMXID()))
client, err := mautrix.NewClient(helper.bridge.AS.HomeserverURL, "", "")
if err != nil {
return nil, err
}
resp, err := client.Login(&mautrix.ReqLogin{
Type: mautrix.AuthTypePassword,
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: string(helper.bridge.AS.BotMXID())},
Password: hex.EncodeToString(mac.Sum(nil)),
DeviceID: deviceID,
InitialDeviceDisplayName: "Skype Bridge",
StoreCredentials: true,
})
if err != nil {
return nil, err
}
if len(deviceID) == 0 {
helper.store.DeviceID = resp.DeviceID
}
helper.store.DeviceID = resp.DeviceID
return client, nil
}
@ -203,15 +176,15 @@ func (helper *CryptoHelper) Encrypt(roomID id.RoomID, evtType event.Type, conten
helper.log.Debugfln("Got %v while encrypting event for %s, sharing group session and trying again...", err, roomID)
users, err := helper.store.GetRoomMembers(roomID)
if err != nil {
return nil, errors.Wrap(err, "failed to get room member list")
return nil, fmt.Errorf("failed to get room member list: %w", err)
}
err = helper.mach.ShareGroupSession(roomID, users)
if err != nil {
return nil, errors.Wrap(err, "failed to share group session")
return nil, fmt.Errorf("failed to share group session: %w", err)
}
encrypted, err = helper.mach.EncryptMegolmEvent(roomID, evtType, &content)
if err != nil {
return nil, errors.Wrap(err, "failed to encrypt event after re-sharing group session")
return nil, fmt.Errorf("failed to encrypt event after re-sharing group session: %w", err)
}
}
return encrypted, nil
@ -226,7 +199,23 @@ type cryptoSyncer struct {
}
func (syncer *cryptoSyncer) ProcessResponse(resp *mautrix.RespSync, since string) error {
syncer.ProcessSyncResponse(resp, since)
done := make(chan struct{})
go func() {
defer func() {
if err := recover(); err != nil {
syncer.Log.Error("Processing sync response (%s) panicked: %v\n%s", since, err, debug.Stack())
}
done <- struct{}{}
}()
syncer.Log.Trace("Starting sync response handling (%s)", since)
syncer.ProcessSyncResponse(resp, since)
syncer.Log.Trace("Successfully handled sync response (%s)", since)
}()
select {
case <-done:
case <-time.After(30 * time.Second):
syncer.Log.Warn("Handling sync response (%s) is taking unusually long", since)
}
return nil
}

View File

@ -4,7 +4,7 @@ import (
"crypto/hmac"
"crypto/sha512"
"encoding/hex"
"strings"
"fmt"
"time"
"github.com/pkg/errors"
@ -28,7 +28,7 @@ func (puppet *Puppet) SwitchCustomMXID(accessToken string, mxid id.UserID) error
puppet.CustomMXID = mxid
puppet.AccessToken = accessToken
err := puppet.StartCustomMXID()
err := puppet.StartCustomMXID(false)
if err != nil {
return err
}
@ -46,11 +46,17 @@ func (puppet *Puppet) SwitchCustomMXID(accessToken string, mxid id.UserID) error
}
func (puppet *Puppet) loginWithSharedSecret(mxid id.UserID) (string, error) {
mac := hmac.New(sha512.New, []byte(puppet.bridge.Config.Bridge.LoginSharedSecret))
_, homeserver, _ := mxid.Parse()
puppet.log.Debugfln("Logging into %s with shared secret", mxid)
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{
Type: "m.login.password",
Identifier: mautrix.UserIdentifier{Type: "m.id.user", User: string(mxid)},
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)),
DeviceID: "Skype Bridge",
InitialDeviceDisplayName: "Skype Bridge",
@ -61,6 +67,36 @@ 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
@ -89,7 +125,7 @@ func (puppet *Puppet) clearCustomMXID() {
puppet.customUser = nil
}
func (puppet *Puppet) StartCustomMXID() error {
func (puppet *Puppet) StartCustomMXID(reloginOnFail bool) error {
if len(puppet.CustomMXID) == 0 {
puppet.clearCustomMXID()
return nil
@ -101,21 +137,12 @@ func (puppet *Puppet) StartCustomMXID() error {
}
resp, err := intent.Whoami()
if err != nil {
// if strings.Index(err.Error(), "M_UNKNOWN_TOKEN (HTTP 401)") > -1 {
// puppet.log.Debugln("StartCustomMXID UpdateAccessToken: ", puppet.MXID)
// if puppet.customUser != nil {
// err, _ = puppet.customUser.UpdateAccessToken(puppet)
// }
// }
if puppet.customUser != nil {
err, _ = puppet.updateCustomAccessTokenIfExpired(err, puppet.customUser)
}
if err != nil {
if !reloginOnFail || (errors.Is(err, mautrix.MUnknownToken) && !puppet.tryRelogin(err, "initializing double puppeting")) {
puppet.clearCustomMXID()
return err
}
}
if resp.UserID != puppet.CustomMXID {
intent.AccessToken = puppet.AccessToken
} else if resp.UserID != puppet.CustomMXID {
puppet.clearCustomMXID()
return ErrMismatchingMXID
}
@ -126,17 +153,6 @@ func (puppet *Puppet) StartCustomMXID() error {
return nil
}
func (puppet *Puppet) updateCustomAccessTokenIfExpired(err error, user *User) (newErr error, accessToken string) {
if user == nil {
return err, ""
}
puppet.log.Debugln("StartCustomMXID UpdateAccessToken: ", puppet.MXID)
if strings.Index(err.Error(), "M_UNKNOWN_TOKEN (HTTP 401)") > -1 {
return user.UpdateAccessToken(puppet)
}
return err, ""
}
func (puppet *Puppet) startSyncing() {
if !puppet.bridge.Config.Bridge.SyncWithCustomPuppets {
return
@ -249,9 +265,30 @@ func (puppet *Puppet) handleTypingEvent(portal *Portal, evt *event.Event) {
//}
}
func (puppet *Puppet) tryRelogin(cause error, action string) bool {
if !puppet.bridge.Config.CanAutoDoublePuppet(puppet.CustomMXID) {
return false
}
puppet.log.Debugfln("Trying to relogin after '%v' while %s", cause, action)
accessToken, err := puppet.loginWithSharedSecret(puppet.CustomMXID)
if err != nil {
puppet.log.Errorfln("Failed to relogin after '%v' while %s: %v", cause, action, err)
return false
}
puppet.log.Infofln("Successfully relogined after '%v' while %s", cause, action)
puppet.AccessToken = accessToken
return true
}
func (puppet *Puppet) OnFailedSync(res *mautrix.RespSync, err error) (time.Duration, error) {
puppet.log.Warnln("Sync error:", err)
err, _ = puppet.updateCustomAccessTokenIfExpired(err, puppet.customUser)
if errors.Is(err, mautrix.MUnknownToken) {
if !puppet.tryRelogin(err, "syncing") {
return 0, err
}
puppet.customIntent.AccessToken = puppet.AccessToken
return 0, nil
}
return 10 * time.Second, err
}

View File

@ -129,12 +129,19 @@ bridge:
# Whether or not to sync with custom puppets to receive EDUs that
# are not normally sent to appservices.
sync_with_custom_puppets: true
# 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 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
login_shared_secret_map:
example.com: foobar
# Whether or not to invite own Skype user's Matrix puppet into private
# chat portals when backfilling if needed.

View File

@ -323,7 +323,7 @@ func (bridge *Bridge) StartUsers() {
for _, loopuppet := range bridge.GetAllPuppetsWithCustomMXID() {
go func(puppet *Puppet) {
puppet.log.Debugln("Starting custom puppet", puppet.CustomMXID)
err := puppet.StartCustomMXID()
err := puppet.StartCustomMXID(true)
if err != nil {
puppet.log.Errorln("Failed to start custom puppet:", err)
}

View File

@ -346,8 +346,9 @@ func (portal *Portal) getMessageIntentSkype(user *User, info skype.Resource) *ap
return portal.bridge.GetPuppetByJID(info.SendId + skypeExt.NewUserSuffix).IntentFor(portal)
}
func (portal *Portal) handlePrivateChatFromMe(fromMe bool) func() {
if portal.IsPrivateChat() && fromMe && len(portal.bridge.Config.Bridge.LoginSharedSecret) == 0 {
func (portal *Portal) handlePrivateChatFromMe(user *User, fromMe bool) func() {
_, homeserver, _ := user.MXID.Parse()
if portal.IsPrivateChat() && fromMe && len(portal.bridge.Config.Bridge.LoginSharedSecretMap[homeserver]) == 0 {
var privateChatPuppet *Puppet
var privateChatPuppetInvited bool
privateChatPuppet = portal.bridge.GetPuppetByJID(portal.Key.Receiver)
@ -378,10 +379,9 @@ func (portal *Portal) startHandlingSkype(source *User, info skype.Resource) (*ap
} else {
portal.log.Debugfln("Starting handling of %s (ts: %d)", info.Id, info.Timestamp)
portal.lastMessageTs = uint64(info.Timestamp)
return portal.getMessageIntentSkype(source, info), portal.handlePrivateChatFromMe(info.GetFromMe(source.Conn.Conn))
return portal.getMessageIntentSkype(source, info), portal.handlePrivateChatFromMe(source, info.GetFromMe(source.Conn.Conn))
}
fmt.Println()
fmt.Printf("portal startHandling: %+v", "but nil")
portal.log.Debugfln("startHandlingSkype: %+v", "but nil")
return nil, nil
}
@ -945,7 +945,8 @@ func (portal *Portal) beginBackfill() func() {
portal.privateChatBackfillInvitePuppet = nil
portal.backfillLock.Unlock()
if privateChatPuppet != nil && privateChatPuppetInvited {
if len(portal.bridge.Config.Bridge.LoginSharedSecret) > 0 {
_, homeserver, _ := privateChatPuppet.MXID.Parse()
if len(portal.bridge.Config.Bridge.LoginSharedSecretMap[homeserver]) > 0 {
_, _ = privateChatPuppet.DefaultIntent().LeaveRoom(portal.MXID)
}
}
@ -1512,7 +1513,7 @@ func (portal *Portal) trySendMessage(intent *appservice.IntentAPI, eventType eve
resp, err = portal.sendMessage(intent, eventType, content, message.Timestamp*1000)
if err != nil {
portal.log.Errorfln("Failed to handle message %s: %v", message.Id, err)
if strings.Index(err.Error(), "M_UNKNOWN_TOKEN (HTTP 401)") > -1 {
if errors.Is(err, mautrix.MUnknownToken) {
puppet := source.bridge.GetPuppetByJID(source.JID)
err, accessToken := source.UpdateAccessToken(puppet)
if err == nil && accessToken != "" {

View File

@ -6,6 +6,7 @@ import (
skypeExt "github.com/kelaresg/matrix-skype/skype-ext"
"net/http"
"regexp"
"sync"
"strings"
log "maunium.net/go/maulogger/v2"
@ -159,6 +160,8 @@ type Puppet struct {
customIntent *appservice.IntentAPI
customTypingIn map[id.RoomID]bool
customUser *User
syncLock sync.Mutex
}
func (puppet *Puppet) PhoneNumber() string {

34
user.go
View File

@ -445,34 +445,32 @@ func (user *User) PostLogin() {
}
func (user *User) tryAutomaticDoublePuppeting() {
fmt.Println("tryAutomaticDoublePuppeting")
if len(user.bridge.Config.Bridge.LoginSharedSecret) == 0 {
fmt.Println("tryAutomaticDoublePuppeting-1", user.bridge.Config.Bridge.LoginSharedSecret)
// Automatic login not enabled
return
} else if _, homeserver, _ := user.MXID.Parse(); homeserver != user.bridge.Config.Homeserver.Domain {
fmt.Println("tryAutomaticDoublePuppeting-2", user.MXID)
fmt.Println("tryAutomaticDoublePuppeting-21", homeserver)
fmt.Println("tryAutomaticDoublePuppeting--3", user.bridge.Config.Homeserver.Domain)
// user is on another homeserver
if !user.bridge.Config.CanAutoDoublePuppet(user.MXID) {
return
}
fmt.Println("tryAutomaticDoublePuppeting1")
user.log.Debugln("Checking if double puppeting needs to be enabled")
puppet := user.bridge.GetPuppetByJID(user.JID)
if len(puppet.CustomMXID) > 0 {
user.log.Debugln("User already has double-puppeting enabled")
// Custom puppet already enabled
return
}
fmt.Println("tryAutomaticDoublePuppeting2", user.MXID)
_, _ = user.UpdateAccessToken(puppet)
accessToken, err := puppet.loginWithSharedSecret(user.MXID)
if err != nil {
user.log.Warnln("Failed to login with shared secret:", err)
return
}
err = puppet.SwitchCustomMXID(accessToken, user.MXID)
if err != nil {
puppet.log.Warnln("Failed to switch to auto-logined custom puppet:", err)
return
}
user.log.Infoln("Successfully automatically enabled custom puppet")
}
func (user *User) UpdateAccessToken(puppet *Puppet) (err error, accessToken string) {
if len(user.bridge.Config.Bridge.LoginSharedSecret) == 0 {
return errors.New("you didn't set LoginSharedSecret"), ""
} else if _, homeserver, _ := user.MXID.Parse(); homeserver != user.bridge.Config.Homeserver.Domain {
// user is on another homeserver
return errors.New("user is on another homeServer"), ""
if !puppet.bridge.Config.CanAutoDoublePuppet(user.MXID) {
return errors.New("you didn't set LoginSharedSecret or user is on another homeServer"), ""
}
accessToken, err = puppet.loginWithSharedSecret(user.MXID)
if err != nil {