mirror of
https://mau.dev/mautrix/go.git
synced 2026-03-14 14:25:53 +01:00
Mostly implement double puppeting
This commit is contained in:
parent
464b7bc44b
commit
1ff72aeffb
6 changed files with 125 additions and 98 deletions
|
|
@ -26,12 +26,12 @@ func fnLoginMatrix(ce *bridgev2.CommandEvent) {
|
|||
ce.Reply("**Usage:** `login-matrix <access token>`")
|
||||
return
|
||||
}
|
||||
//err := ce.User.SwitchCustomMXID(ce.Args[0], ce.User.GetMXID())
|
||||
//if err != nil {
|
||||
// ce.Reply("Failed to enable double puppeting: %v", err)
|
||||
//} else {
|
||||
// ce.Reply("Successfully switched puppet")
|
||||
//}
|
||||
err := ce.User.LoginDoublePuppet(ce.Ctx, ce.Args[0])
|
||||
if err != nil {
|
||||
ce.Reply("Failed to enable double puppeting: %v", err)
|
||||
} else {
|
||||
ce.Reply("Successfully switched puppets")
|
||||
}
|
||||
}
|
||||
|
||||
var CommandPingMatrix = &bridgev2.FullHandler{
|
||||
|
|
@ -41,16 +41,25 @@ var CommandPingMatrix = &bridgev2.FullHandler{
|
|||
Section: bridgev2.HelpSectionAuth,
|
||||
Description: "Ping the Matrix server with the double puppet.",
|
||||
},
|
||||
RequiresLogin: true,
|
||||
}
|
||||
|
||||
func fnPingMatrix(ce *bridgev2.CommandEvent) {
|
||||
//resp, err := puppet.CustomIntent().Whoami(ce.Ctx)
|
||||
//if err != nil {
|
||||
// ce.Reply("Failed to validate Matrix login: %v", err)
|
||||
//} else {
|
||||
// ce.Reply("Confirmed valid access token for %s / %s", resp.UserID, resp.DeviceID)
|
||||
//}
|
||||
intent := ce.User.DoublePuppet(ce.Ctx)
|
||||
if intent == nil {
|
||||
ce.Reply("You don't have double puppeting enabled.")
|
||||
return
|
||||
}
|
||||
asIntent := intent.(*ASIntent)
|
||||
resp, err := asIntent.Matrix.Whoami(ce.Ctx)
|
||||
if err != nil {
|
||||
ce.Reply("Failed to validate Matrix login: %v", err)
|
||||
} else {
|
||||
if asIntent.Matrix.SetAppServiceUserID && resp.DeviceID == "" {
|
||||
ce.Reply("Confirmed valid access token for %s (appservice double puppeting)", resp.UserID)
|
||||
} else {
|
||||
ce.Reply("Confirmed valid access token for %s / %s", resp.UserID, resp.DeviceID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var CommandLogoutMatrix = &bridgev2.FullHandler{
|
||||
|
|
@ -64,11 +73,10 @@ var CommandLogoutMatrix = &bridgev2.FullHandler{
|
|||
}
|
||||
|
||||
func fnLogoutMatrix(ce *bridgev2.CommandEvent) {
|
||||
//puppet := ce.User.GetIDoublePuppet()
|
||||
//if puppet == nil || puppet.CustomIntent() == nil {
|
||||
// ce.Reply("You don't have double puppeting enabled.")
|
||||
// return
|
||||
//}
|
||||
//puppet.ClearCustomMXID()
|
||||
//ce.Reply("Successfully disabled double puppeting.")
|
||||
if ce.User.AccessToken == "" {
|
||||
ce.Reply("You don't have double puppeting enabled.")
|
||||
return
|
||||
}
|
||||
ce.User.LogoutDoublePuppet(ce.Ctx)
|
||||
ce.Reply("Successfully disabled double puppeting.")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,7 +105,10 @@ func (br *Connector) Init(bridge *bridgev2.Bridge) {
|
|||
br.EventProcessor.On(event.StateMember, br.handleRoomEvent)
|
||||
br.Bot = br.AS.BotIntent()
|
||||
br.Crypto = NewCryptoHelper(br)
|
||||
br.Bridge.Commands.AddHandlers(CommandDiscardMegolmSession, CommandSetPowerLevel)
|
||||
br.Bridge.Commands.AddHandlers(
|
||||
CommandDiscardMegolmSession, CommandSetPowerLevel,
|
||||
CommandLoginMatrix, CommandPingMatrix, CommandLogoutMatrix,
|
||||
)
|
||||
br.Provisioning = &ProvisioningAPI{br: br}
|
||||
br.DoublePuppet = newDoublePuppetUtil(br)
|
||||
}
|
||||
|
|
@ -367,9 +370,15 @@ func (br *Connector) FormatGhostMXID(userID networkid.UserID) id.UserID {
|
|||
return id.NewUserID(localpart, br.Config.Homeserver.Domain)
|
||||
}
|
||||
|
||||
func (br *Connector) UserIntent(user *bridgev2.User) bridgev2.MatrixAPI {
|
||||
// TODO implement double puppeting
|
||||
return nil
|
||||
func (br *Connector) NewUserIntent(ctx context.Context, userID id.UserID, accessToken string) (bridgev2.MatrixAPI, string, error) {
|
||||
intent, newToken, err := br.DoublePuppet.Setup(ctx, userID, accessToken)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNoAccessToken) {
|
||||
err = nil
|
||||
}
|
||||
return nil, accessToken, err
|
||||
}
|
||||
return &ASIntent{Connector: br, Matrix: intent}, newToken, nil
|
||||
}
|
||||
|
||||
func (br *Connector) BotIntent() bridgev2.MatrixAPI {
|
||||
|
|
|
|||
|
|
@ -8,9 +8,6 @@ package matrix
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
|
@ -24,8 +21,7 @@ import (
|
|||
)
|
||||
|
||||
type doublePuppetUtil struct {
|
||||
br *Connector
|
||||
log zerolog.Logger
|
||||
br *Connector
|
||||
|
||||
discoveryCache map[string]string
|
||||
discoveryCacheLock sync.Mutex
|
||||
|
|
@ -34,7 +30,6 @@ type doublePuppetUtil struct {
|
|||
func newDoublePuppetUtil(br *Connector) *doublePuppetUtil {
|
||||
return &doublePuppetUtil{
|
||||
br: br,
|
||||
log: br.Log.With().Str("component", "double puppet").Logger(),
|
||||
discoveryCache: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
|
@ -58,7 +53,7 @@ func (dp *doublePuppetUtil) newClient(ctx context.Context, mxid id.UserID, acces
|
|||
}
|
||||
homeserverURL = resp.Homeserver.BaseURL
|
||||
dp.discoveryCache[homeserver] = homeserverURL
|
||||
dp.log.Debug().
|
||||
zerolog.Ctx(ctx).Debug().
|
||||
Str("homeserver", homeserver).
|
||||
Str("url", homeserverURL).
|
||||
Str("user_id", mxid.String()).
|
||||
|
|
@ -85,47 +80,6 @@ func (dp *doublePuppetUtil) newIntent(ctx context.Context, mxid id.UserID, acces
|
|||
return ia, nil
|
||||
}
|
||||
|
||||
func (dp *doublePuppetUtil) autoLogin(ctx context.Context, mxid id.UserID, loginSecret string) (string, error) {
|
||||
dp.log.Debug().Str("user_id", mxid.String()).Msg("Logging into user account with shared secret")
|
||||
client, err := dp.newClient(ctx, mxid, "")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create mautrix client to log in: %v", err)
|
||||
}
|
||||
bridgeName := fmt.Sprintf("%s Bridge", dp.br.Bridge.Network.GetName().DisplayName)
|
||||
req := mautrix.ReqLogin{
|
||||
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: string(mxid)},
|
||||
DeviceID: id.DeviceID(bridgeName),
|
||||
InitialDeviceDisplayName: bridgeName,
|
||||
}
|
||||
if loginSecret == "appservice" {
|
||||
client.AccessToken = dp.br.AS.Registration.AppToken
|
||||
req.Type = mautrix.AuthTypeAppservice
|
||||
} else {
|
||||
loginFlows, err := client.GetLoginFlows(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get supported login flows: %w", err)
|
||||
}
|
||||
mac := hmac.New(sha512.New, []byte(loginSecret))
|
||||
mac.Write([]byte(mxid))
|
||||
token := hex.EncodeToString(mac.Sum(nil))
|
||||
switch {
|
||||
case loginFlows.HasFlow(mautrix.AuthTypeDevtureSharedSecret):
|
||||
req.Type = mautrix.AuthTypeDevtureSharedSecret
|
||||
req.Token = token
|
||||
case loginFlows.HasFlow(mautrix.AuthTypePassword):
|
||||
req.Type = mautrix.AuthTypePassword
|
||||
req.Password = token
|
||||
default:
|
||||
return "", fmt.Errorf("no supported auth types for shared secret auth found")
|
||||
}
|
||||
}
|
||||
resp, err := client.Login(ctx, &req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.AccessToken, nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrMismatchingMXID = errors.New("whoami result does not match custom mxid")
|
||||
ErrNoAccessToken = errors.New("no access token provided")
|
||||
|
|
@ -135,14 +89,13 @@ var (
|
|||
const useConfigASToken = "appservice-config"
|
||||
const asTokenModePrefix = "as_token:"
|
||||
|
||||
func (dp *doublePuppetUtil) Setup(ctx context.Context, mxid id.UserID, savedAccessToken string, reloginOnFail bool) (intent *appservice.IntentAPI, newAccessToken string, err error) {
|
||||
func (dp *doublePuppetUtil) Setup(ctx context.Context, mxid id.UserID, savedAccessToken string) (intent *appservice.IntentAPI, newAccessToken string, err error) {
|
||||
if len(mxid) == 0 {
|
||||
err = ErrNoMXID
|
||||
return
|
||||
}
|
||||
_, homeserver, _ := mxid.Parse()
|
||||
loginSecret, hasSecret := dp.br.Config.DoublePuppet.Secrets[homeserver]
|
||||
// Special case appservice: prefix to not login and use it as an as_token directly.
|
||||
if hasSecret && strings.HasPrefix(loginSecret, asTokenModePrefix) {
|
||||
intent, err = dp.newIntent(ctx, mxid, strings.TrimPrefix(loginSecret, asTokenModePrefix))
|
||||
if err != nil {
|
||||
|
|
@ -157,16 +110,9 @@ func (dp *doublePuppetUtil) Setup(ctx context.Context, mxid id.UserID, savedAcce
|
|||
}
|
||||
}
|
||||
return intent, useConfigASToken, err
|
||||
}
|
||||
if savedAccessToken == "" || savedAccessToken == useConfigASToken {
|
||||
if reloginOnFail && hasSecret {
|
||||
savedAccessToken, err = dp.autoLogin(ctx, mxid, loginSecret)
|
||||
} else {
|
||||
err = ErrNoAccessToken
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if savedAccessToken == "" || savedAccessToken == useConfigASToken {
|
||||
err = ErrNoAccessToken
|
||||
return
|
||||
}
|
||||
intent, err = dp.newIntent(ctx, mxid, savedAccessToken)
|
||||
if err != nil {
|
||||
|
|
@ -174,17 +120,12 @@ func (dp *doublePuppetUtil) Setup(ctx context.Context, mxid id.UserID, savedAcce
|
|||
}
|
||||
var resp *mautrix.RespWhoami
|
||||
resp, err = intent.Whoami(ctx)
|
||||
if err != nil {
|
||||
if reloginOnFail && hasSecret && errors.Is(err, mautrix.MUnknownToken) {
|
||||
intent.AccessToken, err = dp.autoLogin(ctx, mxid, loginSecret)
|
||||
if err == nil {
|
||||
newAccessToken = intent.AccessToken
|
||||
}
|
||||
if err == nil {
|
||||
if resp.UserID != mxid {
|
||||
err = ErrMismatchingMXID
|
||||
} else {
|
||||
newAccessToken = savedAccessToken
|
||||
}
|
||||
} else if resp.UserID != mxid {
|
||||
err = ErrMismatchingMXID
|
||||
} else {
|
||||
newAccessToken = savedAccessToken
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ type MatrixConnector interface {
|
|||
FormatGhostMXID(userID networkid.UserID) id.UserID
|
||||
|
||||
GhostIntent(userID id.UserID) MatrixAPI
|
||||
UserIntent(user *User) MatrixAPI
|
||||
NewUserIntent(ctx context.Context, userID id.UserID, accessToken string) (MatrixAPI, string, error)
|
||||
BotIntent() MatrixAPI
|
||||
|
||||
SendMessageStatus(ctx context.Context, status *MessageStatus, evt *MessageStatusEventInfo)
|
||||
|
|
|
|||
|
|
@ -691,12 +691,12 @@ func (portal *Portal) handleRemoteEvent(source *UserLogin, evt RemoteEvent) {
|
|||
func (portal *Portal) getIntentFor(ctx context.Context, sender EventSender, source *UserLogin) MatrixAPI {
|
||||
var intent MatrixAPI
|
||||
if sender.IsFromMe {
|
||||
intent = portal.Bridge.Matrix.UserIntent(source.User)
|
||||
intent = source.User.DoublePuppet(ctx)
|
||||
}
|
||||
if intent == nil && sender.SenderLogin != "" {
|
||||
senderLogin := portal.Bridge.GetCachedUserLoginByID(sender.SenderLogin)
|
||||
if senderLogin != nil {
|
||||
intent = portal.Bridge.Matrix.UserIntent(senderLogin.User)
|
||||
intent = senderLogin.User.DoublePuppet(ctx)
|
||||
}
|
||||
}
|
||||
if intent == nil {
|
||||
|
|
@ -1184,7 +1184,7 @@ func (portal *Portal) SyncParticipants(ctx context.Context, members []networkid.
|
|||
for _, login := range loginsInPortal {
|
||||
if login.Client.IsThisUser(ctx, member) {
|
||||
isLoggedInUser = true
|
||||
userIntent := portal.Bridge.Matrix.UserIntent(login.User)
|
||||
userIntent := login.User.DoublePuppet(ctx)
|
||||
if userIntent != nil {
|
||||
expectedIntents[i] = userIntent
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ package bridgev2
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
|
@ -25,6 +26,10 @@ type User struct {
|
|||
|
||||
CommandState atomic.Pointer[CommandState]
|
||||
|
||||
doublePuppetIntent MatrixAPI
|
||||
doublePuppetInitialized bool
|
||||
doublePuppetLock sync.Mutex
|
||||
|
||||
logins map[networkid.UserLoginID]*UserLogin
|
||||
}
|
||||
|
||||
|
|
@ -83,3 +88,67 @@ func (br *Bridge) GetExistingUserByMXID(ctx context.Context, userID id.UserID) (
|
|||
defer br.cacheLock.Unlock()
|
||||
return br.unlockedGetUserByMXID(ctx, userID, true)
|
||||
}
|
||||
|
||||
func (user *User) LogoutDoublePuppet(ctx context.Context) {
|
||||
user.doublePuppetLock.Lock()
|
||||
defer user.doublePuppetLock.Unlock()
|
||||
user.AccessToken = ""
|
||||
err := user.Save(ctx)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to save removed access token")
|
||||
}
|
||||
user.doublePuppetIntent = nil
|
||||
user.doublePuppetInitialized = false
|
||||
}
|
||||
|
||||
func (user *User) LoginDoublePuppet(ctx context.Context, token string) error {
|
||||
if token == "" {
|
||||
return fmt.Errorf("no token provided")
|
||||
}
|
||||
user.doublePuppetLock.Lock()
|
||||
defer user.doublePuppetLock.Unlock()
|
||||
intent, newToken, err := user.Bridge.Matrix.NewUserIntent(ctx, user.MXID, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user.AccessToken = newToken
|
||||
user.doublePuppetIntent = intent
|
||||
user.doublePuppetInitialized = true
|
||||
err = user.Save(ctx)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to save new access token")
|
||||
}
|
||||
if newToken != token {
|
||||
return fmt.Errorf("logging in manually is not supported when automatic double puppeting is enabled")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (user *User) DoublePuppet(ctx context.Context) MatrixAPI {
|
||||
user.doublePuppetLock.Lock()
|
||||
defer user.doublePuppetLock.Unlock()
|
||||
if user.doublePuppetInitialized {
|
||||
return user.doublePuppetIntent
|
||||
}
|
||||
user.doublePuppetInitialized = true
|
||||
log := user.Log.With().Str("action", "setup double puppet").Logger()
|
||||
ctx = log.WithContext(ctx)
|
||||
intent, newToken, err := user.Bridge.Matrix.NewUserIntent(ctx, user.MXID, user.AccessToken)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to create new user intent")
|
||||
return nil
|
||||
}
|
||||
user.doublePuppetIntent = intent
|
||||
if newToken != user.AccessToken {
|
||||
user.AccessToken = newToken
|
||||
err = user.Save(ctx)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to save new access token")
|
||||
}
|
||||
}
|
||||
return intent
|
||||
}
|
||||
|
||||
func (user *User) Save(ctx context.Context) error {
|
||||
return user.Bridge.DB.User.Update(ctx, user.User)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue