mirror of
https://mau.dev/mautrix/go.git
synced 2026-03-14 14:25:53 +01:00
Merge branch 'master' into cross-sign
This commit is contained in:
commit
495d77b3a0
29 changed files with 465 additions and 166 deletions
14
README.md
14
README.md
|
|
@ -1,11 +1,23 @@
|
|||
# mautrix-go
|
||||
[](https://godoc.org/maunium.net/go/mautrix)
|
||||
|
||||
A Golang Matrix framework.
|
||||
A Golang Matrix framework. Used by [gomuks](https://matrix.org/docs/projects/client/gomuks),
|
||||
[go-neb](https://github.com/matrix-org/go-neb), [mautrix-whatsapp](https://github.com/tulir/mautrix-whatsapp)
|
||||
and others.
|
||||
|
||||
Matrix room: [`#maunium:maunium.net`](https://matrix.to/#/#maunium:maunium.net)
|
||||
|
||||
This project is based on [matrix-org/gomatrix](https://github.com/matrix-org/gomatrix).
|
||||
The original project is licensed under [Apache 2.0](https://github.com/matrix-org/gomatrix/blob/master/LICENSE).
|
||||
|
||||
In addition to the basic client API features the original project has, this framework also has:
|
||||
|
||||
* Appservice support (Intent API like mautrix-python, room state storage, etc)
|
||||
* End-to-end encryption support (incl. interactive SAS verification)
|
||||
* Structs for parsing event content
|
||||
* Helpers for parsing and generating Matrix HTML
|
||||
* Helpers for handling push rules
|
||||
|
||||
This project contains modules that are licensed under Apache 2.0:
|
||||
|
||||
* [maunium.net/go/mautrix/crypto/canonicaljson](crypto/canonicaljson)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
|
@ -51,27 +52,38 @@ func (as *AppService) Stop() {
|
|||
return
|
||||
}
|
||||
|
||||
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
_ = as.server.Shutdown(ctx)
|
||||
as.server = nil
|
||||
}
|
||||
|
||||
// CheckServerToken checks if the given request originated from the Matrix homeserver.
|
||||
func (as *AppService) CheckServerToken(w http.ResponseWriter, r *http.Request) bool {
|
||||
query := r.URL.Query()
|
||||
val, ok := query["access_token"]
|
||||
if !ok {
|
||||
func (as *AppService) CheckServerToken(w http.ResponseWriter, r *http.Request) (isValid bool) {
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if len(authHeader) > 0 && strings.HasPrefix(authHeader, "Bearer ") {
|
||||
isValid = authHeader[len("Bearer "):] == as.Registration.ServerToken
|
||||
} else {
|
||||
queryToken := r.URL.Query().Get("access_token")
|
||||
if len(queryToken) > 0 {
|
||||
isValid = queryToken == as.Registration.ServerToken
|
||||
} else {
|
||||
Error{
|
||||
ErrorCode: ErrUnknownToken,
|
||||
HTTPStatus: http.StatusForbidden,
|
||||
Message: "Missing access token",
|
||||
}.Write(w)
|
||||
return
|
||||
}
|
||||
}
|
||||
if !isValid {
|
||||
Error{
|
||||
ErrorCode: ErrForbidden,
|
||||
ErrorCode: ErrUnknownToken,
|
||||
HTTPStatus: http.StatusForbidden,
|
||||
Message: "Bad token supplied.",
|
||||
Message: "Incorrect access token",
|
||||
}.Write(w)
|
||||
return false
|
||||
}
|
||||
for _, str := range val {
|
||||
return str == as.Registration.ServerToken
|
||||
}
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
// PutTransaction handles a /transactions PUT call from the homeserver.
|
||||
|
|
@ -86,7 +98,7 @@ func (as *AppService) PutTransaction(w http.ResponseWriter, r *http.Request) {
|
|||
Error{
|
||||
ErrorCode: ErrNoTransactionID,
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
Message: "Missing transaction ID.",
|
||||
Message: "Missing transaction ID",
|
||||
}.Write(w)
|
||||
return
|
||||
}
|
||||
|
|
@ -94,9 +106,9 @@ func (as *AppService) PutTransaction(w http.ResponseWriter, r *http.Request) {
|
|||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil || len(body) == 0 {
|
||||
Error{
|
||||
ErrorCode: ErrNoBody,
|
||||
ErrorCode: ErrNotJSON,
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
Message: "Missing request body.",
|
||||
Message: "Missing request body",
|
||||
}.Write(w)
|
||||
return
|
||||
}
|
||||
|
|
@ -111,11 +123,19 @@ func (as *AppService) PutTransaction(w http.ResponseWriter, r *http.Request) {
|
|||
if err != nil {
|
||||
as.Log.Warnfln("Failed to parse JSON of transaction %s: %v", txnID, err)
|
||||
Error{
|
||||
ErrorCode: ErrInvalidJSON,
|
||||
ErrorCode: ErrBadJSON,
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
Message: "Failed to parse body JSON.",
|
||||
Message: "Failed to parse body JSON",
|
||||
}.Write(w)
|
||||
} else {
|
||||
if as.Registration.EphemeralEvents {
|
||||
if eventList.EphemeralEvents != nil {
|
||||
as.handleEvents(eventList.EphemeralEvents, event.EphemeralEventType)
|
||||
} else if eventList.SoruEphemeralEvents != nil {
|
||||
as.handleEvents(eventList.SoruEphemeralEvents, event.EphemeralEventType)
|
||||
}
|
||||
}
|
||||
as.handleEvents(eventList.Events, event.UnknownEventType)
|
||||
for _, evt := range eventList.Events {
|
||||
if evt.StateKey != nil {
|
||||
evt.Type.Class = event.StateEventType
|
||||
|
|
@ -134,6 +154,30 @@ func (as *AppService) PutTransaction(w http.ResponseWriter, r *http.Request) {
|
|||
as.lastProcessedTransaction = txnID
|
||||
}
|
||||
|
||||
func (as *AppService) handleEvents(evts []*event.Event, typeClass event.TypeClass) {
|
||||
for _, evt := range evts {
|
||||
if typeClass != event.UnknownEventType {
|
||||
evt.Type.Class = typeClass
|
||||
} else if evt.StateKey != nil {
|
||||
evt.Type.Class = event.StateEventType
|
||||
} else {
|
||||
evt.Type.Class = event.MessageEventType
|
||||
}
|
||||
err := evt.Content.ParseRaw(evt.Type)
|
||||
if err != nil {
|
||||
if evt.ID != "" {
|
||||
as.Log.Debugfln("Failed to parse content of %s (%s): %v", evt.ID, evt.Type.Type, err)
|
||||
} else {
|
||||
as.Log.Debugfln("Failed to parse content of a %s: %v", evt.Type.Type, err)
|
||||
}
|
||||
}
|
||||
if evt.Type.IsState() {
|
||||
as.UpdateState(evt)
|
||||
}
|
||||
as.Events <- evt
|
||||
}
|
||||
}
|
||||
|
||||
// GetRoom handles a /rooms GET call from the homeserver.
|
||||
func (as *AppService) GetRoom(w http.ResponseWriter, r *http.Request) {
|
||||
if !as.CheckServerToken(w, r) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@ import (
|
|||
|
||||
// EventList contains a list of events.
|
||||
type EventList struct {
|
||||
Events []*event.Event `json:"events"`
|
||||
Events []*event.Event `json:"events"`
|
||||
EphemeralEvents []*event.Event `json:"ephemeral"`
|
||||
SoruEphemeralEvents []*event.Event `json:"de.sorunome.msc2409.ephemeral"`
|
||||
}
|
||||
|
||||
// EventListener is a function that receives events.
|
||||
|
|
@ -23,12 +25,14 @@ type EventListener func(evt *event.Event)
|
|||
|
||||
// WriteBlankOK writes a blank OK message as a reply to a HTTP request.
|
||||
func WriteBlankOK(w http.ResponseWriter) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("{}"))
|
||||
}
|
||||
|
||||
// Respond responds to a HTTP request with a JSON object.
|
||||
func Respond(w http.ResponseWriter, data interface{}) error {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
dataStr, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -45,6 +49,7 @@ type Error struct {
|
|||
}
|
||||
|
||||
func (err Error) Write(w http.ResponseWriter) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.WriteHeader(err.HTTPStatus)
|
||||
_ = Respond(w, &err)
|
||||
}
|
||||
|
|
@ -54,13 +59,13 @@ type ErrorCode string
|
|||
|
||||
// Native ErrorCodes
|
||||
const (
|
||||
ErrForbidden ErrorCode = "M_FORBIDDEN"
|
||||
ErrUnknown ErrorCode = "M_UNKNOWN"
|
||||
ErrUnknownToken ErrorCode = "M_UNKNOWN_TOKEN"
|
||||
ErrBadJSON ErrorCode = "M_BAD_JSON"
|
||||
ErrNotJSON ErrorCode = "M_NOT_JSON"
|
||||
ErrUnknown ErrorCode = "M_UNKNOWN"
|
||||
)
|
||||
|
||||
// Custom ErrorCodes
|
||||
const (
|
||||
ErrNoTransactionID ErrorCode = "NET.MAUNIUM.NO_TRANSACTION_ID"
|
||||
ErrNoBody ErrorCode = "NET.MAUNIUM.NO_REQUEST_BODY"
|
||||
ErrInvalidJSON ErrorCode = "NET.MAUNIUM.INVALID_JSON"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ type Registration struct {
|
|||
SenderLocalpart string `yaml:"sender_localpart"`
|
||||
RateLimited bool `yaml:"rate_limited"`
|
||||
Namespaces Namespaces `yaml:"namespaces"`
|
||||
EphemeralEvents bool `yaml:"de.sorunome.msc2409.push_ephemeral,omitempty"`
|
||||
}
|
||||
|
||||
// CreateRegistration creates a Registration with random appservice and homeserver tokens.
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ package crypto
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
|
@ -18,7 +18,7 @@ import (
|
|||
var (
|
||||
IncorrectEncryptedContentType = errors.New("event content is not instance of *event.EncryptedEventContent")
|
||||
NoSessionFound = errors.New("failed to decrypt megolm event: no session with given ID found")
|
||||
DuplicateMessageIndex = errors.New("duplicate message index")
|
||||
DuplicateMessageIndex = errors.New("duplicate megolm message index")
|
||||
WrongRoom = errors.New("encrypted megolm event is not intended for this room")
|
||||
DeviceKeyMismatch = errors.New("device keys in event and verified device info do not match")
|
||||
)
|
||||
|
|
@ -39,14 +39,14 @@ func (mach *OlmMachine) DecryptMegolmEvent(evt *event.Event) (*event.Event, erro
|
|||
}
|
||||
sess, err := mach.CryptoStore.GetGroupSession(evt.RoomID, content.SenderKey, content.SessionID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get group session")
|
||||
return nil, fmt.Errorf("failed to get group session: %w", err)
|
||||
} else if sess == nil {
|
||||
mach.checkIfWedged(evt)
|
||||
return nil, NoSessionFound
|
||||
return nil, fmt.Errorf("%w (ID %s)", NoSessionFound, content.SessionID)
|
||||
}
|
||||
plaintext, messageIndex, err := sess.Internal.Decrypt(content.MegolmCiphertext)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decrypt megolm event")
|
||||
return nil, fmt.Errorf("failed to decrypt megolm event: %w", err)
|
||||
} else if !mach.CryptoStore.ValidateMessageIndex(content.SenderKey, content.SessionID, evt.ID, messageIndex, evt.Timestamp) {
|
||||
return nil, DuplicateMessageIndex
|
||||
}
|
||||
|
|
@ -72,7 +72,7 @@ func (mach *OlmMachine) DecryptMegolmEvent(evt *event.Event) (*event.Event, erro
|
|||
megolmEvt := &megolmEvent{}
|
||||
err = json.Unmarshal(plaintext, &megolmEvt)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse megolm payload")
|
||||
return nil, fmt.Errorf("failed to parse megolm payload: %w", err)
|
||||
} else if megolmEvt.RoomID != evt.RoomID {
|
||||
return nil, WrongRoom
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@ func (mach *OlmMachine) DecryptMegolmEvent(evt *event.Event) (*event.Event, erro
|
|||
if event.IsUnsupportedContentType(err) {
|
||||
mach.Log.Warn("Unsupported event type %s in encrypted event %s", megolmEvt.Type.Repr(), evt.ID)
|
||||
} else {
|
||||
return nil, errors.Wrap(err, "failed to parse content of megolm payload event")
|
||||
return nil, fmt.Errorf("failed to parse content of megolm payload event: %w", err)
|
||||
}
|
||||
}
|
||||
if content.RelatesTo != nil {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ package crypto
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
|
@ -76,7 +76,7 @@ func (mach *OlmMachine) decryptOlmCiphertext(sender id.UserID, deviceID id.Devic
|
|||
mach.Log.Warn("Found matching session yet decryption failed for sender %s with key %s", sender, senderKey)
|
||||
mach.markDeviceForUnwedging(sender, senderKey)
|
||||
}
|
||||
return nil, errors.Wrap(err, "failed to decrypt olm event")
|
||||
return nil, fmt.Errorf("failed to decrypt olm event: %w", err)
|
||||
}
|
||||
|
||||
// Decryption failed with every known session or no known sessions, let's try to create a new session.
|
||||
|
|
@ -92,13 +92,13 @@ func (mach *OlmMachine) decryptOlmCiphertext(sender id.UserID, deviceID id.Devic
|
|||
session, err := mach.createInboundSession(senderKey, ciphertext)
|
||||
if err != nil {
|
||||
mach.markDeviceForUnwedging(sender, senderKey)
|
||||
return nil, errors.Wrap(err, "failed to create new session from prekey message")
|
||||
return nil, fmt.Errorf("failed to create new session from prekey message: %w", err)
|
||||
}
|
||||
mach.Log.Trace("Created inbound session %s for %s/%s (sender key: %s)", session.ID(), sender, deviceID, senderKey)
|
||||
mach.Log.Debug("Created inbound olm session %s for %s/%s (sender key: %s)", session.ID(), sender, deviceID, senderKey)
|
||||
|
||||
plaintext, err = session.Decrypt(ciphertext, olmType)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decrypt olm event with session created from prekey message")
|
||||
return nil, fmt.Errorf("failed to decrypt olm event with session created from prekey message: %w", err)
|
||||
}
|
||||
|
||||
err = mach.CryptoStore.UpdateSession(senderKey, session)
|
||||
|
|
@ -110,7 +110,7 @@ func (mach *OlmMachine) decryptOlmCiphertext(sender id.UserID, deviceID id.Devic
|
|||
var olmEvt DecryptedOlmEvent
|
||||
err = json.Unmarshal(plaintext, &olmEvt)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse olm payload")
|
||||
return nil, fmt.Errorf("failed to parse olm payload: %w", err)
|
||||
}
|
||||
if sender != olmEvt.Sender {
|
||||
return nil, SenderMismatch
|
||||
|
|
@ -122,7 +122,7 @@ func (mach *OlmMachine) decryptOlmCiphertext(sender id.UserID, deviceID id.Devic
|
|||
|
||||
err = olmEvt.Content.ParseRaw(olmEvt.Type)
|
||||
if err != nil && !event.IsUnsupportedContentType(err) {
|
||||
return nil, errors.Wrap(err, "failed to parse content of olm payload event")
|
||||
return nil, fmt.Errorf("failed to parse content of olm payload event: %w", err)
|
||||
}
|
||||
|
||||
olmEvt.SenderKey = senderKey
|
||||
|
|
@ -133,13 +133,13 @@ func (mach *OlmMachine) decryptOlmCiphertext(sender id.UserID, deviceID id.Devic
|
|||
func (mach *OlmMachine) tryDecryptOlmCiphertext(senderKey id.SenderKey, olmType id.OlmMsgType, ciphertext string) ([]byte, error) {
|
||||
sessions, err := mach.CryptoStore.GetSessions(senderKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get session for %s", senderKey)
|
||||
return nil, fmt.Errorf("failed to get session for %s: %w", senderKey, err)
|
||||
}
|
||||
for _, session := range sessions {
|
||||
if olmType == id.OlmMsgTypePreKey {
|
||||
matches, err := session.Internal.MatchesInboundSession(ciphertext)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to check if ciphertext matches inbound session")
|
||||
return nil, fmt.Errorf("failed to check if ciphertext matches inbound session: %w", err)
|
||||
} else if !matches {
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/crypto/olm"
|
||||
|
|
@ -163,7 +164,7 @@ func (mach *OlmMachine) validateDevice(userID id.UserID, deviceID id.DeviceID, d
|
|||
|
||||
ok, err := olm.VerifySignatureJSON(deviceKeys, userID, deviceID.String(), signingKey)
|
||||
if err != nil {
|
||||
return existing, errors.Wrap(err, "failed to verify signature")
|
||||
return existing, fmt.Errorf("failed to verify signature: %w", err)
|
||||
} else if !ok {
|
||||
return existing, InvalidKeySignature
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ package crypto
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/event"
|
||||
|
|
@ -52,7 +52,7 @@ func (mach *OlmMachine) EncryptMegolmEvent(roomID id.RoomID, evtType event.Type,
|
|||
mach.Log.Trace("Encrypting event of type %s for %s", evtType.Type, roomID)
|
||||
session, err := mach.CryptoStore.GetOutboundGroupSession(roomID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get outbound group session")
|
||||
return nil, fmt.Errorf("failed to get outbound group session: %w", err)
|
||||
} else if session == nil {
|
||||
return nil, NoGroupSession
|
||||
}
|
||||
|
|
@ -89,6 +89,11 @@ func (mach *OlmMachine) newOutboundGroupSession(roomID id.RoomID) *OutboundGroup
|
|||
return session
|
||||
}
|
||||
|
||||
type deviceSessionWrapper struct {
|
||||
session *OlmSession
|
||||
identity *DeviceIdentity
|
||||
}
|
||||
|
||||
// ShareGroupSession shares a group session for a specific room with all the devices of the given user list.
|
||||
//
|
||||
// For devices with TrustStateBlacklisted, a m.room_key.withheld event with code=m.blacklisted is sent.
|
||||
|
|
@ -97,7 +102,7 @@ func (mach *OlmMachine) ShareGroupSession(roomID id.RoomID, users []id.UserID) e
|
|||
mach.Log.Debug("Sharing group session for room %s to %v", roomID, users)
|
||||
session, err := mach.CryptoStore.GetOutboundGroupSession(roomID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get previous outbound group session")
|
||||
return fmt.Errorf("failed to get previous outbound group session: %w", err)
|
||||
} else if session != nil && session.Shared && !session.Expired() {
|
||||
return AlreadyShared
|
||||
}
|
||||
|
|
@ -105,8 +110,9 @@ func (mach *OlmMachine) ShareGroupSession(roomID id.RoomID, users []id.UserID) e
|
|||
session = mach.newOutboundGroupSession(roomID)
|
||||
}
|
||||
|
||||
toDevice := &mautrix.ReqSendToDevice{Messages: make(map[id.UserID]map[id.DeviceID]*event.Content)}
|
||||
withheldCount := 0
|
||||
toDeviceWithheld := &mautrix.ReqSendToDevice{Messages: make(map[id.UserID]map[id.DeviceID]*event.Content)}
|
||||
olmSessions := make(map[id.UserID]map[id.DeviceID]deviceSessionWrapper)
|
||||
missingSessions := make(map[id.UserID]map[id.DeviceID]*DeviceIdentity)
|
||||
missingUserSessions := make(map[id.DeviceID]*DeviceIdentity)
|
||||
var fetchKeys []id.UserID
|
||||
|
|
@ -122,9 +128,10 @@ func (mach *OlmMachine) ShareGroupSession(roomID id.RoomID, users []id.UserID) e
|
|||
mach.Log.Trace("%s has no devices, skipping", userID)
|
||||
} else {
|
||||
mach.Log.Trace("Trying to encrypt group session %s for %s", session.ID(), userID)
|
||||
toDevice.Messages[userID] = make(map[id.DeviceID]*event.Content)
|
||||
toDeviceWithheld.Messages[userID] = make(map[id.DeviceID]*event.Content)
|
||||
mach.encryptGroupSessionForUser(session, userID, devices, toDevice.Messages[userID], toDeviceWithheld.Messages[userID], missingUserSessions)
|
||||
olmSessions[userID] = make(map[id.DeviceID]deviceSessionWrapper)
|
||||
mach.findOlmSessionsForUser(session, userID, devices, olmSessions[userID], toDeviceWithheld.Messages[userID], missingUserSessions)
|
||||
withheldCount += len(toDeviceWithheld.Messages[userID])
|
||||
if len(missingUserSessions) > 0 {
|
||||
missingSessions[userID] = missingUserSessions
|
||||
missingUserSessions = make(map[id.DeviceID]*DeviceIdentity)
|
||||
|
|
@ -132,9 +139,6 @@ func (mach *OlmMachine) ShareGroupSession(roomID id.RoomID, users []id.UserID) e
|
|||
if len(toDeviceWithheld.Messages[userID]) == 0 {
|
||||
delete(toDeviceWithheld.Messages, userID)
|
||||
}
|
||||
if len(toDevice.Messages[userID]) == 0 {
|
||||
delete(toDevice.Messages, userID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -146,10 +150,12 @@ func (mach *OlmMachine) ShareGroupSession(roomID id.RoomID, users []id.UserID) e
|
|||
}
|
||||
}
|
||||
|
||||
mach.Log.Trace("Creating missing outbound sessions")
|
||||
err = mach.createOutboundSessions(missingSessions)
|
||||
if err != nil {
|
||||
mach.Log.Error("Failed to create missing outbound sessions: %v", err)
|
||||
if len(missingSessions) > 0 {
|
||||
mach.Log.Trace("Creating missing outbound sessions")
|
||||
err = mach.createOutboundSessions(missingSessions)
|
||||
if err != nil {
|
||||
mach.Log.Error("Failed to create missing outbound sessions: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for userID, devices := range missingSessions {
|
||||
|
|
@ -157,10 +163,10 @@ func (mach *OlmMachine) ShareGroupSession(roomID id.RoomID, users []id.UserID) e
|
|||
// No missing sessions
|
||||
continue
|
||||
}
|
||||
output, ok := toDevice.Messages[userID]
|
||||
output, ok := olmSessions[userID]
|
||||
if !ok {
|
||||
output = make(map[id.DeviceID]*event.Content)
|
||||
toDevice.Messages[userID] = output
|
||||
output = make(map[id.DeviceID]deviceSessionWrapper)
|
||||
olmSessions[userID] = output
|
||||
}
|
||||
withheld, ok := toDeviceWithheld.Messages[userID]
|
||||
if !ok {
|
||||
|
|
@ -168,35 +174,62 @@ func (mach *OlmMachine) ShareGroupSession(roomID id.RoomID, users []id.UserID) e
|
|||
toDeviceWithheld.Messages[userID] = withheld
|
||||
}
|
||||
mach.Log.Trace("Trying to encrypt group session %s for %s (post-fetch retry)", session.ID(), userID)
|
||||
mach.encryptGroupSessionForUser(session, userID, devices, output, withheld, nil)
|
||||
mach.findOlmSessionsForUser(session, userID, devices, output, withheld, nil)
|
||||
withheldCount += len(toDeviceWithheld.Messages[userID])
|
||||
if len(toDeviceWithheld.Messages[userID]) == 0 {
|
||||
delete(toDeviceWithheld.Messages, userID)
|
||||
}
|
||||
if len(toDevice.Messages[userID]) == 0 {
|
||||
delete(toDevice.Messages, userID)
|
||||
}
|
||||
|
||||
err = mach.encryptAndSendGroupSession(session, olmSessions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to share group session: %w", err)
|
||||
}
|
||||
|
||||
if len(toDeviceWithheld.Messages) > 0 {
|
||||
mach.Log.Trace("Sending to-device messages to %d devices of %d users to report withheld keys in %s", withheldCount, len(toDeviceWithheld.Messages), roomID)
|
||||
// TODO remove the next 4 lines once clients support m.room_key.withheld
|
||||
_, err = mach.Client.SendToDevice(event.ToDeviceOrgMatrixRoomKeyWithheld, toDeviceWithheld)
|
||||
if err != nil {
|
||||
mach.Log.Warn("Failed to report withheld keys in %s (legacy event type): %v", roomID, err)
|
||||
}
|
||||
_, err = mach.Client.SendToDevice(event.ToDeviceRoomKeyWithheld, toDeviceWithheld)
|
||||
if err != nil {
|
||||
mach.Log.Warn("Failed to report withheld keys in %s: %v", roomID, err)
|
||||
}
|
||||
}
|
||||
|
||||
mach.Log.Trace("Sending to-device to %d users to share group session for %s", len(toDevice.Messages), roomID)
|
||||
_, err = mach.Client.SendToDevice(event.ToDeviceEncrypted, toDevice)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to share group session")
|
||||
}
|
||||
|
||||
mach.Log.Trace("Sending to-device messages to %d users to report withheld keys in %s", len(toDeviceWithheld.Messages), roomID)
|
||||
// TODO remove the next line once clients support m.room_key.withheld
|
||||
_, _ = mach.Client.SendToDevice(event.ToDeviceOrgMatrixRoomKeyWithheld, toDeviceWithheld)
|
||||
_, err = mach.Client.SendToDevice(event.ToDeviceRoomKeyWithheld, toDeviceWithheld)
|
||||
if err != nil {
|
||||
mach.Log.Warn("Failed to report withheld keys in %s: %v", roomID, err)
|
||||
}
|
||||
|
||||
mach.Log.Debug("Group session for %s successfully shared", roomID)
|
||||
mach.Log.Debug("Group session %s for %s successfully shared", session.ID(), roomID)
|
||||
session.Shared = true
|
||||
return mach.CryptoStore.AddOutboundGroupSession(session)
|
||||
}
|
||||
|
||||
func (mach *OlmMachine) encryptGroupSessionForUser(session *OutboundGroupSession, userID id.UserID, devices map[id.DeviceID]*DeviceIdentity, output, withheld map[id.DeviceID]*event.Content, missingOutput map[id.DeviceID]*DeviceIdentity) {
|
||||
func (mach *OlmMachine) encryptAndSendGroupSession(session *OutboundGroupSession, olmSessions map[id.UserID]map[id.DeviceID]deviceSessionWrapper) error {
|
||||
deviceCount := 0
|
||||
toDevice := &mautrix.ReqSendToDevice{Messages: make(map[id.UserID]map[id.DeviceID]*event.Content)}
|
||||
for userID, sessions := range olmSessions {
|
||||
if len(sessions) == 0 {
|
||||
continue
|
||||
}
|
||||
output := make(map[id.DeviceID]*event.Content)
|
||||
toDevice.Messages[userID] = output
|
||||
for deviceID, device := range sessions {
|
||||
device.session.Lock()
|
||||
// We intentionally defer in a loop as it's the safest way of making sure nothing gets locked permanently.
|
||||
defer device.session.Unlock()
|
||||
content := mach.encryptOlmEvent(device.session, device.identity, event.ToDeviceRoomKey, session.ShareContent())
|
||||
output[deviceID] = &event.Content{Parsed: content}
|
||||
deviceCount++
|
||||
mach.Log.Trace("Encrypted group session %s for %s of %s", session.ID(), deviceID, userID)
|
||||
}
|
||||
}
|
||||
|
||||
mach.Log.Trace("Sending to-device to %d devices of %d users to share group session %s", deviceCount, len(toDevice.Messages), session.ID())
|
||||
_, err := mach.Client.SendToDevice(event.ToDeviceEncrypted, toDevice)
|
||||
return err
|
||||
}
|
||||
|
||||
func (mach *OlmMachine) findOlmSessionsForUser(session *OutboundGroupSession, userID id.UserID, devices map[id.DeviceID]*DeviceIdentity, output map[id.DeviceID]deviceSessionWrapper, withheld map[id.DeviceID]*event.Content, missingOutput map[id.DeviceID]*DeviceIdentity) {
|
||||
for deviceID, device := range devices {
|
||||
userKey := UserDevice{UserID: userID, DeviceID: deviceID}
|
||||
if state := session.Users[userKey]; state != OGSNotShared {
|
||||
|
|
@ -233,10 +266,11 @@ func (mach *OlmMachine) encryptGroupSessionForUser(session *OutboundGroupSession
|
|||
missingOutput[deviceID] = device
|
||||
}
|
||||
} else {
|
||||
content := mach.encryptOlmEvent(deviceSession, device, event.ToDeviceRoomKey, session.ShareContent())
|
||||
output[deviceID] = &event.Content{Parsed: content}
|
||||
output[deviceID] = deviceSessionWrapper{
|
||||
session: deviceSession,
|
||||
identity: device,
|
||||
}
|
||||
session.Users[userKey] = OGSAlreadyShared
|
||||
mach.Log.Trace("Encrypted group session %s for %s of %s", session.ID(), deviceID, userID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ package crypto
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"fmt"
|
||||
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/crypto/olm"
|
||||
|
|
@ -69,7 +68,7 @@ func (mach *OlmMachine) createOutboundSessions(input map[id.UserID]map[id.Device
|
|||
Timeout: 10 * 1000,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to claim keys")
|
||||
return fmt.Errorf("failed to claim keys: %w", err)
|
||||
}
|
||||
for userID, user := range resp.OneTimeKeys {
|
||||
for deviceID, oneTimeKeys := range user {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import (
|
|||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
|
||||
"maunium.net/go/mautrix/crypto/olm"
|
||||
|
|
@ -91,7 +90,7 @@ func exportSessions(sessions []*InboundGroupSession) ([]ExportedSession, error)
|
|||
for i, session := range sessions {
|
||||
key, err := session.Internal.Export(session.Internal.FirstKnownIndex())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to export session")
|
||||
return nil, fmt.Errorf("failed to export session: %w", err)
|
||||
}
|
||||
export[i] = ExportedSession{
|
||||
Algorithm: id.AlgorithmMegolmV1,
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"maunium.net/go/mautrix/crypto/olm"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
|
@ -85,7 +85,7 @@ func decryptKeyExport(passphrase string, exportData []byte) ([]ExportedSession,
|
|||
var sessionsJSON []ExportedSession
|
||||
err := json.Unmarshal(unencryptedData, &sessionsJSON)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "invalid export json")
|
||||
return nil, fmt.Errorf("invalid export json: %w", err)
|
||||
}
|
||||
return sessionsJSON, nil
|
||||
}
|
||||
|
|
@ -97,7 +97,7 @@ func (mach *OlmMachine) importExportedRoomKey(session ExportedSession) (bool, er
|
|||
|
||||
igsInternal, err := olm.InboundGroupSessionImport([]byte(session.SessionKey))
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to import session")
|
||||
return false, fmt.Errorf("failed to import session: %w", err)
|
||||
} else if igsInternal.ID() != session.SessionID {
|
||||
return false, ErrMismatchingExportedSessionID
|
||||
}
|
||||
|
|
@ -116,8 +116,9 @@ func (mach *OlmMachine) importExportedRoomKey(session ExportedSession) (bool, er
|
|||
}
|
||||
err = mach.CryptoStore.PutGroupSession(igs.RoomID, igs.SenderKey, igs.ID(), igs)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to store imported session")
|
||||
return false, fmt.Errorf("failed to store imported session: %w", err)
|
||||
}
|
||||
mach.markSessionReceived(igs.ID())
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@ func (mach *OlmMachine) importForwardedRoomKey(evt *DecryptedOlmEvent, content *
|
|||
mach.Log.Error("Failed to store new inbound group session: %v", err)
|
||||
return false
|
||||
}
|
||||
mach.markSessionReceived(content.SessionID)
|
||||
mach.Log.Trace("Created inbound group session %s/%s/%s", content.RoomID, content.SenderKey, content.SessionID)
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"maunium.net/go/mautrix/crypto/olm"
|
||||
"maunium.net/go/mautrix/crypto/ssss"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
|
@ -52,6 +52,9 @@ type OlmMachine struct {
|
|||
roomKeyRequestFilled *sync.Map
|
||||
keyVerificationTransactionState *sync.Map
|
||||
|
||||
keyWaiters map[id.SessionID]chan struct{}
|
||||
keyWaitersLock sync.Mutex
|
||||
|
||||
CrossSigningKeys *CrossSigningKeysCache
|
||||
crossSigningPubkeys *CrossSigningPublicKeysCache
|
||||
}
|
||||
|
|
@ -86,6 +89,8 @@ func NewOlmMachine(client *mautrix.Client, log Logger, cryptoStore Store, stateS
|
|||
|
||||
roomKeyRequestFilled: &sync.Map{},
|
||||
keyVerificationTransactionState: &sync.Map{},
|
||||
|
||||
keyWaiters: make(map[id.SessionID]chan struct{}),
|
||||
}
|
||||
mach.AllowKeyShare = mach.defaultAllowKeyShare
|
||||
return mach
|
||||
|
|
@ -266,7 +271,7 @@ func (mach *OlmMachine) GetOrFetchDevice(userID id.UserID, deviceID id.DeviceID)
|
|||
// get device identity
|
||||
device, err := mach.CryptoStore.GetDevice(userID, deviceID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get sender device from store")
|
||||
return nil, fmt.Errorf("failed to get sender device from store: %w", err)
|
||||
} else if device != nil {
|
||||
return device, nil
|
||||
}
|
||||
|
|
@ -276,9 +281,9 @@ func (mach *OlmMachine) GetOrFetchDevice(userID id.UserID, deviceID id.DeviceID)
|
|||
if device, ok = devices[deviceID]; ok {
|
||||
return device, nil
|
||||
}
|
||||
return nil, errors.Errorf("Failed to get identity for device %v", deviceID)
|
||||
return nil, fmt.Errorf("didn't get identity for device %s of %s", deviceID, userID)
|
||||
}
|
||||
return nil, errors.Errorf("Error fetching devices for user %v", userID)
|
||||
return nil, fmt.Errorf("didn't get any devices for %s", userID)
|
||||
}
|
||||
|
||||
// SendEncryptedToDevice sends an Olm-encrypted event to the given user device.
|
||||
|
|
@ -298,9 +303,12 @@ func (mach *OlmMachine) SendEncryptedToDevice(device *DeviceIdentity, content ev
|
|||
return err
|
||||
}
|
||||
if olmSess == nil {
|
||||
return errors.Errorf("Did not find created outbound session for device %v", device.DeviceID)
|
||||
return fmt.Errorf("didn't find created outbound session for device %s of %s", device.DeviceID, device.UserID)
|
||||
}
|
||||
|
||||
olmSess.Lock()
|
||||
defer olmSess.Unlock()
|
||||
|
||||
encrypted := mach.encryptOlmEvent(olmSess, device, event.ToDeviceForwardedRoomKey, content)
|
||||
encryptedContent := &event.Content{Parsed: &encrypted}
|
||||
|
||||
|
|
@ -329,8 +337,40 @@ func (mach *OlmMachine) createGroupSession(senderKey id.SenderKey, signingKey id
|
|||
err = mach.CryptoStore.PutGroupSession(roomID, senderKey, sessionID, igs)
|
||||
if err != nil {
|
||||
mach.Log.Error("Failed to store new inbound group session: %v", err)
|
||||
return
|
||||
}
|
||||
mach.markSessionReceived(sessionID)
|
||||
mach.Log.Debug("Received inbound group session %s / %s / %s", roomID, senderKey, sessionID)
|
||||
}
|
||||
|
||||
func (mach *OlmMachine) markSessionReceived(id id.SessionID) {
|
||||
mach.keyWaitersLock.Lock()
|
||||
ch, ok := mach.keyWaiters[id]
|
||||
if ok {
|
||||
close(ch)
|
||||
delete(mach.keyWaiters, id)
|
||||
}
|
||||
mach.keyWaitersLock.Unlock()
|
||||
}
|
||||
|
||||
// WaitForSession waits for the given Megolm session to arrive.
|
||||
func (mach *OlmMachine) WaitForSession(roomID id.RoomID, senderKey id.SenderKey, sessionID id.SessionID, timeout time.Duration) bool {
|
||||
mach.keyWaitersLock.Lock()
|
||||
ch, ok := mach.keyWaiters[sessionID]
|
||||
if !ok {
|
||||
ch := make(chan struct{})
|
||||
mach.keyWaiters[sessionID] = ch
|
||||
}
|
||||
mach.keyWaitersLock.Unlock()
|
||||
select {
|
||||
case <-ch:
|
||||
return true
|
||||
case <-time.After(timeout):
|
||||
sess, err := mach.CryptoStore.GetGroupSession(roomID, senderKey, sessionID)
|
||||
// Check if the session somehow appeared in the store without telling us
|
||||
// We accept withheld sessions as received, as then the decryption attempt will show the error.
|
||||
return sess != nil || errors.Is(err, ErrGroupSessionWithheld)
|
||||
}
|
||||
mach.Log.Trace("Created inbound group session %s/%s/%s", roomID, senderKey, sessionID)
|
||||
}
|
||||
|
||||
func (mach *OlmMachine) receiveRoomKey(evt *DecryptedOlmEvent, content *event.RoomKeyEventContent) {
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"maunium.net/go/mautrix/crypto/olm"
|
||||
"maunium.net/go/mautrix/event"
|
||||
|
||||
|
|
@ -43,6 +43,20 @@ type OlmSession struct {
|
|||
Internal olm.Session
|
||||
ExpirationMixin
|
||||
id id.SessionID
|
||||
// This is unexported so gob wouldn't insist on trying to marshaling it
|
||||
lock sync.Locker
|
||||
}
|
||||
|
||||
func (session *OlmSession) SetLock(lock sync.Locker) {
|
||||
session.lock = lock
|
||||
}
|
||||
|
||||
func (session *OlmSession) Lock() {
|
||||
session.lock.Lock()
|
||||
}
|
||||
|
||||
func (session *OlmSession) Unlock() {
|
||||
session.lock.Unlock()
|
||||
}
|
||||
|
||||
func (session *OlmSession) ID() id.SessionID {
|
||||
|
|
@ -55,6 +69,7 @@ func (session *OlmSession) ID() id.SessionID {
|
|||
func wrapSession(session *olm.Session) *OlmSession {
|
||||
return &OlmSession{
|
||||
Internal: *session,
|
||||
lock: &sync.Mutex{},
|
||||
ExpirationMixin: ExpirationMixin{
|
||||
TimeMixin: TimeMixin{
|
||||
CreationTime: time.Now(),
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ import (
|
|||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"maunium.net/go/mautrix/crypto/olm"
|
||||
"maunium.net/go/mautrix/crypto/sql_store_upgrade"
|
||||
|
|
@ -31,6 +31,9 @@ type SQLCryptoStore struct {
|
|||
SyncToken string
|
||||
PickleKey []byte
|
||||
Account *OlmAccount
|
||||
|
||||
olmSessionCache map[id.SenderKey]map[id.SessionID]*OlmSession
|
||||
olmSessionCacheLock sync.Mutex
|
||||
}
|
||||
|
||||
var _ Store = (*SQLCryptoStore)(nil)
|
||||
|
|
@ -45,6 +48,8 @@ func NewSQLCryptoStore(db *sql.DB, dialect string, accountID string, deviceID id
|
|||
PickleKey: pickleKey,
|
||||
AccountID: accountID,
|
||||
DeviceID: deviceID,
|
||||
|
||||
olmSessionCache: make(map[id.SenderKey]map[id.SessionID]*OlmSession),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -125,7 +130,12 @@ func (store *SQLCryptoStore) GetAccount() (*OlmAccount, error) {
|
|||
|
||||
// HasSession returns whether there is an Olm session for the given sender key.
|
||||
func (store *SQLCryptoStore) HasSession(key id.SenderKey) bool {
|
||||
// TODO this may need to be changed if olm sessions start expiring
|
||||
store.olmSessionCacheLock.Lock()
|
||||
cache, ok := store.olmSessionCache[key]
|
||||
store.olmSessionCacheLock.Unlock()
|
||||
if ok && len(cache) > 0 {
|
||||
return true
|
||||
}
|
||||
var sessionID id.SessionID
|
||||
err := store.DB.QueryRow("SELECT session_id FROM crypto_olm_session WHERE sender_key=$1 AND account_id=$2 LIMIT 1",
|
||||
key, store.AccountID).Scan(&sessionID)
|
||||
|
|
@ -137,53 +147,88 @@ func (store *SQLCryptoStore) HasSession(key id.SenderKey) bool {
|
|||
|
||||
// GetSessions returns all the known Olm sessions for a sender key.
|
||||
func (store *SQLCryptoStore) GetSessions(key id.SenderKey) (OlmSessionList, error) {
|
||||
rows, err := store.DB.Query("SELECT session, created_at, last_used FROM crypto_olm_session WHERE sender_key=$1 AND account_id=$2 ORDER BY session_id",
|
||||
rows, err := store.DB.Query("SELECT session_id, session, created_at, last_used FROM crypto_olm_session WHERE sender_key=$1 AND account_id=$2 ORDER BY session_id",
|
||||
key, store.AccountID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list := OlmSessionList{}
|
||||
store.olmSessionCacheLock.Lock()
|
||||
defer store.olmSessionCacheLock.Unlock()
|
||||
cache := store.getOlmSessionCache(key)
|
||||
for rows.Next() {
|
||||
sess := OlmSession{Internal: *olm.NewBlankSession()}
|
||||
sess := OlmSession{Internal: *olm.NewBlankSession(), lock: &sync.Mutex{}}
|
||||
var sessionBytes []byte
|
||||
err := rows.Scan(&sessionBytes, &sess.CreationTime, &sess.UseTime)
|
||||
var sessionID id.SessionID
|
||||
err := rows.Scan(&sessionID, &sessionBytes, &sess.CreationTime, &sess.UseTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if existing, ok := cache[sessionID]; ok {
|
||||
list = append(list, existing)
|
||||
} else {
|
||||
err = sess.Internal.Unpickle(sessionBytes, store.PickleKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list = append(list, &sess)
|
||||
cache[sess.ID()] = &sess
|
||||
}
|
||||
err = sess.Internal.Unpickle(sessionBytes, store.PickleKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list = append(list, &sess)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (store *SQLCryptoStore) getOlmSessionCache(key id.SenderKey) map[id.SessionID]*OlmSession {
|
||||
data, ok := store.olmSessionCache[key]
|
||||
if !ok {
|
||||
data = make(map[id.SessionID]*OlmSession)
|
||||
store.olmSessionCache[key] = data
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// GetLatestSession retrieves the Olm session for a given sender key from the database that has the largest ID.
|
||||
func (store *SQLCryptoStore) GetLatestSession(key id.SenderKey) (*OlmSession, error) {
|
||||
row := store.DB.QueryRow("SELECT session, created_at, last_used FROM crypto_olm_session WHERE sender_key=$1 AND account_id=$2 ORDER BY session_id DESC LIMIT 1",
|
||||
store.olmSessionCacheLock.Lock()
|
||||
defer store.olmSessionCacheLock.Unlock()
|
||||
|
||||
row := store.DB.QueryRow("SELECT session_id, session, created_at, last_used FROM crypto_olm_session WHERE sender_key=$1 AND account_id=$2 ORDER BY session_id DESC LIMIT 1",
|
||||
key, store.AccountID)
|
||||
sess := OlmSession{Internal: *olm.NewBlankSession()}
|
||||
|
||||
sess := OlmSession{Internal: *olm.NewBlankSession(), lock: &sync.Mutex{}}
|
||||
var sessionBytes []byte
|
||||
err := row.Scan(&sessionBytes, &sess.CreationTime, &sess.UseTime)
|
||||
var sessionID id.SessionID
|
||||
|
||||
err := row.Scan(&sessionID, &sessionBytes, &sess.CreationTime, &sess.UseTime)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sess, sess.Internal.Unpickle(sessionBytes, store.PickleKey)
|
||||
|
||||
cache := store.getOlmSessionCache(key)
|
||||
if oldSess, ok := cache[sessionID]; ok {
|
||||
return oldSess, nil
|
||||
} else if err = sess.Internal.Unpickle(sessionBytes, store.PickleKey); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
cache[sessionID] = &sess
|
||||
return &sess, nil
|
||||
}
|
||||
}
|
||||
|
||||
// AddSession persists an Olm session for a sender in the database.
|
||||
func (store *SQLCryptoStore) AddSession(key id.SenderKey, session *OlmSession) error {
|
||||
store.olmSessionCacheLock.Lock()
|
||||
defer store.olmSessionCacheLock.Unlock()
|
||||
sessionBytes := session.Internal.Pickle(store.PickleKey)
|
||||
_, err := store.DB.Exec("INSERT INTO crypto_olm_session (session_id, sender_key, session, created_at, last_used, account_id) VALUES ($1, $2, $3, $4, $5, $6)",
|
||||
session.ID(), key, sessionBytes, session.CreationTime, session.UseTime, store.AccountID)
|
||||
store.getOlmSessionCache(key)[session.ID()] = session
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateSession replaces the Olm session for a sender in the database.
|
||||
func (store *SQLCryptoStore) UpdateSession(key id.SenderKey, session *OlmSession) error {
|
||||
func (store *SQLCryptoStore) UpdateSession(_ id.SenderKey, session *OlmSession) error {
|
||||
sessionBytes := session.Internal.Pickle(store.PickleKey)
|
||||
_, err := store.DB.Exec("UPDATE crypto_olm_session SET session=$1, last_used=$2 WHERE session_id=$3 AND account_id=$4",
|
||||
sessionBytes, session.UseTime, session.ID(), store.AccountID)
|
||||
|
|
@ -224,7 +269,7 @@ func (store *SQLCryptoStore) GetGroupSession(roomID id.RoomID, senderKey id.Send
|
|||
} else if err != nil {
|
||||
return nil, err
|
||||
} else if withheldCode.Valid {
|
||||
return nil, ErrGroupSessionWithheld
|
||||
return nil, fmt.Errorf("%w (%s)", ErrGroupSessionWithheld, withheldCode.String)
|
||||
}
|
||||
igs := olm.NewBlankInboundGroupSession()
|
||||
err = igs.Unpickle(sessionBytes, store.PickleKey)
|
||||
|
|
@ -492,18 +537,18 @@ func (store *SQLCryptoStore) PutDevices(userID id.UserID, devices map[id.DeviceI
|
|||
err = fmt.Errorf("unsupported dialect %s", store.Dialect)
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to add user to tracked users list")
|
||||
return fmt.Errorf("failed to add user to tracked users list: %w", err)
|
||||
}
|
||||
|
||||
_, err = tx.Exec("DELETE FROM crypto_device WHERE user_id=$1", userID)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return errors.Wrap(err, "failed to delete old devices")
|
||||
return fmt.Errorf("failed to delete old devices: %w", err)
|
||||
}
|
||||
if len(devices) == 0 {
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to commit changes (no devices added)")
|
||||
return fmt.Errorf("failed to commit changes (no devices added): %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -533,12 +578,12 @@ func (store *SQLCryptoStore) PutDevices(userID id.UserID, devices map[id.DeviceI
|
|||
_, err = tx.Exec("INSERT INTO crypto_device (user_id, device_id, identity_key, signing_key, trust, deleted, name) VALUES "+valueString, values...)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return errors.Wrap(err, "failed to insert new devices")
|
||||
return fmt.Errorf("failed to insert new devices: %w", err)
|
||||
}
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to commit changes")
|
||||
return fmt.Errorf("failed to commit changes: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@ package sql_store_upgrade
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type upgradeFunc func(*sql.Tx, string) error
|
||||
|
||||
var ErrUnknownDialect = errors.New("unknown dialect")
|
||||
|
||||
var Upgrades = [...]upgradeFunc{
|
||||
func(tx *sql.Tx, _ string) error {
|
||||
for _, query := range []string{
|
||||
|
|
@ -153,7 +154,7 @@ var Upgrades = [...]upgradeFunc{
|
|||
}
|
||||
}
|
||||
} else {
|
||||
return errors.New("unknown dialect: " + dialect)
|
||||
return fmt.Errorf("%w (%s)", ErrUnknownDialect, dialect)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
|
@ -203,7 +204,7 @@ var Upgrades = [...]upgradeFunc{
|
|||
return err
|
||||
}
|
||||
} else {
|
||||
return errors.New("unknown dialect: " + dialect)
|
||||
return fmt.Errorf("%w (%s)", ErrUnknownDialect, dialect)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ package crypto
|
|||
import (
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"sync"
|
||||
|
|
@ -65,6 +66,7 @@ var ErrGroupSessionWithheld = errors.New("group session has been withheld")
|
|||
// General implementation details:
|
||||
// * Get methods should not return errors if the requested data does not exist in the store, they should simply return nil.
|
||||
// * Update methods may assume that the pointer is the same as what has earlier been added to or fetched from the store.
|
||||
// * OlmSessions should be cached so that the mutex works. Alternatively, implementations can use OlmSession.SetLock to provide a custom mutex implementation.
|
||||
type Store interface {
|
||||
// Flush ensures that everything in the store is persisted to disk.
|
||||
// This doesn't have to do anything, e.g. for database-backed implementations that persist everything immediately.
|
||||
|
|
@ -311,10 +313,10 @@ func (gs *GobStore) GetGroupSession(roomID id.RoomID, senderKey id.SenderKey, se
|
|||
gs.lock.Lock()
|
||||
session, ok := gs.getGroupSessions(roomID, senderKey)[sessionID]
|
||||
if !ok {
|
||||
_, ok := gs.getWithheldGroupSessions(roomID, senderKey)[sessionID]
|
||||
withheld, ok := gs.getWithheldGroupSessions(roomID, senderKey)[sessionID]
|
||||
gs.lock.Unlock()
|
||||
if ok {
|
||||
return nil, ErrGroupSessionWithheld
|
||||
return nil, fmt.Errorf("%w (%s)", ErrGroupSessionWithheld, withheld.Code)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ package crypto
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sort"
|
||||
|
|
@ -19,8 +20,6 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/crypto/canonicaljson"
|
||||
"maunium.net/go/mautrix/crypto/olm"
|
||||
|
|
@ -28,11 +27,14 @@ import (
|
|||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
// ErrUnknownTransaction is returned when a key verification message is received with an unknown transaction ID.
|
||||
var ErrUnknownTransaction = errors.New("Unknown transaction")
|
||||
|
||||
// ErrUnknownVerificationMethod is returned when the verification method in a received m.key.verification.start is unknown.
|
||||
var ErrUnknownVerificationMethod = errors.New("Unknown verification method")
|
||||
var (
|
||||
ErrUnknownUserForTransaction = errors.New("unknown user for transaction")
|
||||
ErrTransactionAlreadyExists = errors.New("transaction already exists")
|
||||
// ErrUnknownTransaction is returned when a key verification message is received with an unknown transaction ID.
|
||||
ErrUnknownTransaction = errors.New("unknown transaction")
|
||||
// ErrUnknownVerificationMethod is returned when the verification method in a received m.key.verification.start is unknown.
|
||||
ErrUnknownVerificationMethod = errors.New("unknown verification method")
|
||||
)
|
||||
|
||||
type VerificationHooks interface {
|
||||
// VerifySASMatch receives the generated SAS and its method, as well as the device that is being verified.
|
||||
|
|
@ -133,7 +135,7 @@ func (mach *OlmMachine) getTransactionState(transactionID string, userID id.User
|
|||
_ = mach.SendInRoomSASVerificationCancel(verState.inRoomID, userID, transactionID, reason, event.VerificationCancelUserMismatch)
|
||||
}
|
||||
mach.keyVerificationTransactionState.Delete(userID.String() + ":" + transactionID)
|
||||
return nil, errors.New(reason)
|
||||
return nil, fmt.Errorf("%w %s: %s", ErrUnknownUserForTransaction, transactionID, userID)
|
||||
}
|
||||
return verState, nil
|
||||
}
|
||||
|
|
@ -648,7 +650,7 @@ func (mach *OlmMachine) NewSASVerificationWith(device *DeviceIdentity, hooks Ver
|
|||
verState.startEventCanonical = string(canonical)
|
||||
_, loaded := mach.keyVerificationTransactionState.LoadOrStore(device.UserID.String()+":"+transactionID, verState)
|
||||
if loaded {
|
||||
return "", errors.New("Transaction already exists")
|
||||
return "", ErrTransactionAlreadyExists
|
||||
}
|
||||
|
||||
mach.timeoutAfter(verState, transactionID, timeout)
|
||||
|
|
|
|||
2
error.go
2
error.go
|
|
@ -69,7 +69,7 @@ type HTTPError struct {
|
|||
}
|
||||
|
||||
func (e HTTPError) Is(err error) bool {
|
||||
return errors.Is(e.RespError, err) || errors.Is(e.WrappedError, err)
|
||||
return (e.RespError != nil && errors.Is(e.RespError, err)) || (e.WrappedError != nil && errors.Is(e.WrappedError, err))
|
||||
}
|
||||
|
||||
func (e HTTPError) IsStatus(code int) bool {
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ package event
|
|||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
|
|
@ -58,7 +56,7 @@ func (content *EncryptedEventContent) UnmarshalJSON(data []byte) error {
|
|||
return json.Unmarshal(content.Ciphertext, &content.OlmCiphertext)
|
||||
case id.AlgorithmMegolmV1:
|
||||
if len(content.Ciphertext) == 0 || content.Ciphertext[0] != '"' || content.Ciphertext[len(content.Ciphertext)-1] != '"' {
|
||||
return errors.New("input doesn't look like a JSON string")
|
||||
return id.InputNotJSONString
|
||||
}
|
||||
content.MegolmCiphertext = content.Ciphertext[1 : len(content.Ciphertext)-1]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,8 +32,10 @@ func (tc TypeClass) Name() string {
|
|||
}
|
||||
|
||||
const (
|
||||
// Unknown events
|
||||
UnknownEventType TypeClass = iota
|
||||
// Normal message events
|
||||
MessageEventType TypeClass = iota
|
||||
MessageEventType
|
||||
// State events
|
||||
StateEventType
|
||||
// Ephemeral events
|
||||
|
|
@ -42,8 +44,6 @@ const (
|
|||
AccountDataEventType
|
||||
// Device-to-device events
|
||||
ToDeviceEventType
|
||||
// Unknown events
|
||||
UnknownEventType
|
||||
)
|
||||
|
||||
type Type struct {
|
||||
|
|
|
|||
5
example/go.mod
Normal file
5
example/go.mod
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
module mautrix-example
|
||||
|
||||
go 1.15
|
||||
|
||||
require maunium.net/go/mautrix v0.7.6
|
||||
33
example/go.sum
Normal file
33
example/go.sum
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
|
||||
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/sjson v1.1.1/go.mod h1:yvVuSnpEQv5cYIrO+AT6kw4QVfd5SDZoGIS7/5+fZFs=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
maunium.net/go/maulogger/v2 v2.1.1/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
|
||||
maunium.net/go/mautrix v0.7.6 h1:jB9oCimPq0mVyolwQBC/9N1fu21AU+Ryq837cLf4gOo=
|
||||
maunium.net/go/mautrix v0.7.6/go.mod h1:Va/74MijqaS0DQ3aUqxmFO54/PMfr1LVsCOcGRHbYmo=
|
||||
64
example/main.go
Normal file
64
example/main.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright (C) 2017 Tulir Asokan
|
||||
// Copyright (C) 2018-2020 Luca Weiss
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"os"
|
||||
)
|
||||
|
||||
var homeserver = flag.String("homeserver", "", "Matrix homeserver")
|
||||
var username = flag.String("username", "", "Matrix username localpart")
|
||||
var password = flag.String("password", "", "Matrix password")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if *username == "" || *password == "" || *homeserver == "" {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("Logging into", *homeserver, "as", *username)
|
||||
client, err := mautrix.NewClient(*homeserver, "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err = client.Login(&mautrix.ReqLogin{
|
||||
Type: "m.login.password",
|
||||
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: *username},
|
||||
Password: *password,
|
||||
StoreCredentials: true,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Login successful")
|
||||
|
||||
syncer := client.Syncer.(*mautrix.DefaultSyncer)
|
||||
syncer.OnEventType(event.EventMessage, func(source mautrix.EventSource, evt *event.Event) {
|
||||
fmt.Printf("<%[1]s> %[4]s (%[2]s/%[3]s)\n", evt.Sender, evt.Type.String(), evt.ID, evt.Content.AsMessage().Body)
|
||||
})
|
||||
|
||||
err = client.Sync()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
5
go.mod
5
go.mod
|
|
@ -7,14 +7,13 @@ require (
|
|||
github.com/gorilla/mux v1.7.4
|
||||
github.com/lib/pq v1.7.0
|
||||
github.com/mattn/go-sqlite3 v1.14.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/russross/blackfriday/v2 v2.0.1
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/tidwall/gjson v1.6.0
|
||||
github.com/tidwall/sjson v1.1.1
|
||||
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
|
||||
golang.org/x/net v0.0.0-20201026091529-146b70c837a4
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
maunium.net/go/maulogger/v2 v2.1.1
|
||||
)
|
||||
|
|
|
|||
29
go.sum
29
go.sum
|
|
@ -12,68 +12,55 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku
|
|||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
|
||||
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
|
||||
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
|
||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
|
||||
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
|
||||
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
|
||||
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.0.1 h1:WE4RBSZ1x6McVVC8S/Md+Qse8YUv6HRObAx6ke00NY8=
|
||||
github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/sjson v1.1.1 h1:7h1vk049Jnd5EH9NyzNiEuwYW4b5qgreBbqRC19AS3U=
|
||||
github.com/tidwall/sjson v1.1.1/go.mod h1:yvVuSnpEQv5cYIrO+AT6kw4QVfd5SDZoGIS7/5+fZFs=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d h1:2+ZP7EfsZV7Vvmx3TIqSlSzATMkTAKqM14YGFPoSKjI=
|
||||
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201026091529-146b70c837a4 h1:awiuzyrRjJDb+OXi9ceHO3SDxVoN3JER57mhtqkdQBs=
|
||||
golang.org/x/net v0.0.0-20201026091529-146b70c837a4/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
maunium.net/go/maulogger/v2 v2.1.1 h1:NAZNc6XUFJzgzfewCzVoGkxNAsblLCSSEdtDuIjP0XA=
|
||||
maunium.net/go/maulogger/v2 v2.1.1/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ const (
|
|||
AuthTypeMSISDN = "m.login.msisdn"
|
||||
AuthTypeToken = "m.login.token"
|
||||
AuthTypeDummy = "m.login.dummy"
|
||||
|
||||
AuthTypeAppservice = "uk.half-shot.msc2778.login.application_service"
|
||||
)
|
||||
|
||||
type IdentifierType string
|
||||
|
|
|
|||
11
responses.go
11
responses.go
|
|
@ -119,10 +119,19 @@ type RespRegister struct {
|
|||
|
||||
type RespLoginFlows struct {
|
||||
Flows []struct {
|
||||
Type string `json:"type"`
|
||||
Type AuthType `json:"type"`
|
||||
} `json:"flows"`
|
||||
}
|
||||
|
||||
func (rlf *RespLoginFlows) HasFlow(flowType AuthType) bool {
|
||||
for _, flow := range rlf.Flows {
|
||||
if flow.Type == flowType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RespLogin is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login
|
||||
type RespLogin struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
package mautrix
|
||||
|
||||
const Version = "v0.7.6"
|
||||
const Version = "v0.7.13"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue