Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PseudoID Invites #398

Merged
merged 10 commits into from
Jul 6, 2023
9 changes: 8 additions & 1 deletion eventauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,9 @@ func accumulateStateNeeded(result *StateNeeded, eventType string, sender spec.Se
}
result.Create = true
result.PowerLevels = true
result.Member = append(result.Member, string(sender))
if stateKey != nil {
result.Member = append(result.Member, string(sender), *stateKey)
result.Member = append(result.Member, *stateKey)
}
if content.Membership == spec.Join || content.Membership == spec.Knock || content.Membership == spec.Invite {
result.JoinRules = true
Expand Down Expand Up @@ -887,6 +888,9 @@ func (e *eventAllower) commonChecks(event PDU) error {
if err != nil {
return err
}
if userID == nil {
return errorf("userID not found for sender %q in room %q", event.SenderID(), event.RoomID())
}
if err := e.create.UserIDAllowed(*userID); err != nil {
return err
}
Expand Down Expand Up @@ -990,6 +994,9 @@ func (m *membershipAllower) membershipAllowed(event PDU) error { // nolint: gocy
if err != nil {
return err
}
if sender == nil {
return errorf("userID not found for sender %q in room %q", m.senderID, event.RoomID())
}
if err := m.create.UserIDAllowed(*sender); err != nil {
return err
}
Expand Down
8 changes: 6 additions & 2 deletions eventcrypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,12 @@ func VerifyEventSignatures(ctx context.Context, e PDU, verifier JSONVerifier, us
if err != nil {
return fmt.Errorf("invalid sender userID: %w", err)
}
serverName := sender.Domain()
needed[serverName] = struct{}{}
var serverName spec.ServerName
if sender != nil {
serverName = sender.Domain()
needed[serverName] = struct{}{}
}
// TODO: what do in this case?
devonh marked this conversation as resolved.
Show resolved Hide resolved

verImpl, err := GetRoomVersion(e.Version())
if err != nil {
Expand Down
19 changes: 19 additions & 0 deletions fclient/federationclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type FederationClient interface {
MakeLeave(ctx context.Context, origin, s spec.ServerName, roomID, userID string) (res RespMakeLeave, err error)
SendLeave(ctx context.Context, origin, s spec.ServerName, event gomatrixserverlib.PDU) (err error)
SendInviteV2(ctx context.Context, origin, s spec.ServerName, request InviteV2Request) (res RespInviteV2, err error)
SendInviteV3(ctx context.Context, origin, s spec.ServerName, request InviteV3Request, userID spec.UserID) (res RespInviteV2, err error)

GetEvent(ctx context.Context, origin, s spec.ServerName, eventID string) (res gomatrixserverlib.Transaction, err error)

Expand Down Expand Up @@ -123,6 +124,7 @@ func (ac *federationClient) doRequest(ctx context.Context, r FederationRequest,

var federationPathPrefixV1 = "/_matrix/federation/v1"
var federationPathPrefixV2 = "/_matrix/federation/v2"
var federationPathPrefixV3 = "/_matrix/federation/v3"

// SendTransaction sends a transaction
func (ac *federationClient) SendTransaction(
Expand Down Expand Up @@ -409,6 +411,23 @@ func (ac *federationClient) SendInviteV2(
return
}

// SendInviteV3 sends an invite m.room.member event to an invited server to be
// signed by it. This is used to invite a user that is not on the local server.
// V3 sends a partial event to allow the invitee to populate the mxid_mapping.
func (ac *federationClient) SendInviteV3(
ctx context.Context, origin, s spec.ServerName, request InviteV3Request, userID spec.UserID,
) (res RespInviteV2, err error) {
path := federationPathPrefixV3 + "/invite/" +
url.PathEscape(request.Event().RoomID) + "/" +
url.PathEscape(userID.String())
req := NewFederationRequest("PUT", origin, s, path)
if err = req.SetContent(request); err != nil {
return
}
err = ac.doRequest(ctx, req, &res)
return
}

// ExchangeThirdPartyInvite sends the builder of a m.room.member event of
// "invite" membership derived from a response from invites sent by an identity
// server.
Expand Down
62 changes: 62 additions & 0 deletions fclient/invitev3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package fclient

import (
"encoding/json"

"github.com/matrix-org/gomatrixserverlib"
)

func NewInviteV3Request(event gomatrixserverlib.ProtoEvent, version gomatrixserverlib.RoomVersion, state []gomatrixserverlib.InviteStrippedState) (
request InviteV3Request, err error,
) {
if !gomatrixserverlib.KnownRoomVersion(version) {
err = gomatrixserverlib.UnsupportedRoomVersionError{
Version: version,
}
return
}
request.fields.inviteV2RequestHeaders = inviteV2RequestHeaders{
RoomVersion: version,
InviteRoomState: state,
}
request.fields.Event = event
return
}

// InviteV3Request is used in the body of a /_matrix/federation/v3/invite request.
type InviteV3Request struct {
fields struct {
inviteV2RequestHeaders
Event gomatrixserverlib.ProtoEvent `json:"event"`
}
}

// MarshalJSON implements json.Marshaller
func (i InviteV3Request) MarshalJSON() ([]byte, error) {
return json.Marshal(i.fields)
}

// UnmarshalJSON implements json.Unmarshaller
func (i *InviteV3Request) UnmarshalJSON(data []byte) error {
err := json.Unmarshal(data, &i.fields)
if err != nil {
return err
}
return err
}

// Event returns the invite event.
func (i *InviteV3Request) Event() gomatrixserverlib.ProtoEvent {
return i.fields.Event
}

// RoomVersion returns the room version of the invited room.
func (i *InviteV3Request) RoomVersion() gomatrixserverlib.RoomVersion {
return i.fields.RoomVersion
}

// InviteRoomState returns stripped state events for the room, containing
// enough information for the client to identify the room.
func (i *InviteV3Request) InviteRoomState() []gomatrixserverlib.InviteStrippedState {
return i.fields.InviteRoomState
}
79 changes: 79 additions & 0 deletions fclient/invitev3_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package fclient

import (
"encoding/json"
"testing"

"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/spec"
)

func TestMarshalInviteV3Request(t *testing.T) {
expected := `{"room_version":"org.matrix.msc4014","invite_room_state":[],"event":{"sender":"@test:localhost","room_id":"!19Mp0U9hjajeIiw1:localhost","type":"m.room.name","state_key":"","prev_events":["upCsBqUhNUgT2/+zkzg8TbqdQpWWKQnZpGJc6KcbUC4"],"auth_events":["abjkiDSg1RkuZrbj2jZoGMlQaaj1Ue3Jhi7I7NlKfXY","X7RUj46hM/8sUHNBIFkStbOauPvbDzjSdH4NibYWnko","k9eM6utkCH8vhLW9/oRsH74jOBS/6RVK42iGDFbylno"],"depth":7,"signatures":{"localhost":{"ed25519:u9kP":"5IzSuRXkxvbTp0vZhhXYZeOe+619iG3AybJXr7zfNn/4vHz4TH7qSJVQXSaHHvcTcDodAKHnTG1WDulgO5okAQ"}},"content":{"name":"test3"}}}`

senderID := "@test:localhost"
roomID := "!19Mp0U9hjajeIiw1:localhost"
eventType := "m.room.name"
stateKey := ""
prevEvents := []string{"upCsBqUhNUgT2/+zkzg8TbqdQpWWKQnZpGJc6KcbUC4"}
authEvents := []string{"abjkiDSg1RkuZrbj2jZoGMlQaaj1Ue3Jhi7I7NlKfXY", "X7RUj46hM/8sUHNBIFkStbOauPvbDzjSdH4NibYWnko", "k9eM6utkCH8vhLW9/oRsH74jOBS/6RVK42iGDFbylno"}
depth := int64(7)
signatures := spec.RawJSON(`{"localhost": {"ed25519:u9kP": "5IzSuRXkxvbTp0vZhhXYZeOe+619iG3AybJXr7zfNn/4vHz4TH7qSJVQXSaHHvcTcDodAKHnTG1WDulgO5okAQ"}}`)
content := spec.RawJSON(`{"name":"test3"}`)

output := gomatrixserverlib.ProtoEvent{
SenderID: senderID,
RoomID: roomID,
Type: eventType,
StateKey: &stateKey,
PrevEvents: prevEvents,
AuthEvents: authEvents,
Depth: depth,
Signature: signatures,
Content: content,
}

inviteReq, err := NewInviteV3Request(output, gomatrixserverlib.RoomVersionPseudoIDs, []gomatrixserverlib.InviteStrippedState{})
if err != nil {
t.Fatal(err)
}

j, err := json.Marshal(inviteReq)
if err != nil {
t.Fatal(err)
}

if string(j) != expected {
t.Fatalf("\nresult: %q\nwanted: %q", string(j), expected)
}

var newRequest InviteV3Request
err = json.Unmarshal(j, &newRequest)
if err != nil {
t.Fatal(err)
}

if newRequest.RoomVersion() != gomatrixserverlib.RoomVersionPseudoIDs {
t.Fatalf("unmatched room version. expected: %v, got: %v", gomatrixserverlib.RoomVersionPseudoIDs, newRequest.RoomVersion())
}
if len(newRequest.InviteRoomState()) != 0 {
t.Fatalf("invite room state should not have any events")
}
if newRequest.Event().SenderID != senderID {
t.Fatalf("unmatched senderID. expected: %v, got: %v", newRequest.Event().SenderID, senderID)

}
if newRequest.Event().RoomID != roomID {
t.Fatalf("unmatched roomID. expected: %v, got: %v", newRequest.Event().RoomID, roomID)
}
if newRequest.Event().Type != eventType {
t.Fatalf("unmatched type. expected: %v, got: %v", newRequest.Event().Type, eventType)

}
if *newRequest.Event().StateKey != stateKey {
t.Fatalf("unmatched state key. expected: %v, got: %v", *newRequest.Event().StateKey, stateKey)
}
if newRequest.Event().Depth != depth {
t.Fatalf("unmatched depth. expected: %v, got: %v", newRequest.Event().Depth, depth)
}
}
73 changes: 67 additions & 6 deletions handleinvite.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package gomatrixserverlib
import (
"context"
"fmt"
"time"

"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util"
Expand All @@ -42,6 +43,13 @@ type HandleInviteInput struct {
UserIDQuerier spec.UserIDForSender // Provides userIDs given a senderID
}

type HandleInviteV3Input struct {
HandleInviteInput

InviteProtoEvent ProtoEvent // The original invite event
GetOrCreateSenderID spec.CreateSenderID // Creates, if needed, a new senderID & private key
}

// HandleInvite - Ensures the incoming invite request is valid and signs the event
// to return back to the remote server.
// On success returns a fully formed & signed Invite Event
Expand Down Expand Up @@ -95,26 +103,79 @@ func HandleInvite(ctx context.Context, input HandleInviteInput) (PDU, error) {
return nil, spec.Forbidden("The invite must be signed by the server it originated on")
}

// Sign the event so that other servers will know that we have received the invite.
signedEvent := input.InviteEvent.Sign(
string(input.InvitedUser.Domain()), input.KeyID, input.PrivateKey,
)

return handleInviteCommonChecks(ctx, input, signedEvent, *sender)
}

func HandleInviteV3(ctx context.Context, input HandleInviteV3Input) (PDU, error) {
if input.RoomQuerier == nil || input.MembershipQuerier == nil || input.StateQuerier == nil || input.UserIDQuerier == nil {
panic("Missing valid Querier")
}
if input.Verifier == nil {
panic("Missing valid JSONVerifier")
}

if ctx == nil {
panic("Missing valid Context")
}

// Check that we can accept invites for this room version.
verImpl, err := GetRoomVersion(input.RoomVersion)
if err != nil {
return nil, spec.UnsupportedRoomVersion(
fmt.Sprintf("Room version %q is not supported by this server.", input.RoomVersion),
)
}

// Check that the room ID is correct.
if input.InviteProtoEvent.RoomID != input.RoomID.String() {
return nil, spec.BadJSON("The room ID in the request path must match the room ID in the invite event JSON")
}

// NOTE: If we already have a senderID for this user in this room,
// this could be because they are already invited/joined or were previously.
// In that case, use the existing senderID to complete this invite event.
// Otherwise we need to create a new senderID
invitedSenderID, signingKey, err := input.GetOrCreateSenderID(ctx, input.InvitedUser, input.RoomID, string(input.RoomVersion))
if err != nil {
util.GetLogger(ctx).WithError(err).Error("GetOrCreateSenderID failed")
return nil, spec.InternalServerError{}
}

input.InviteProtoEvent.StateKey = (*string)(&invitedSenderID)

// Sign the event so that other servers will know that we have received the invite.
keyID := KeyID("ed25519:1")
origin := spec.ServerName("self")
fullEventBuilder := verImpl.NewEventBuilderFromProtoEvent(&input.InviteProtoEvent)
fullEvent, err := fullEventBuilder.Build(time.Now(), origin, keyID, signingKey)
if err != nil {
util.GetLogger(ctx).WithError(err).Error("failed building invite event")
return nil, spec.InternalServerError{}
}

return handleInviteCommonChecks(ctx, input.HandleInviteInput, fullEvent, spec.UserID{})
}

func handleInviteCommonChecks(ctx context.Context, input HandleInviteInput, event PDU, sender spec.UserID) (PDU, error) {
isKnownRoom, err := input.RoomQuerier.IsKnownRoom(ctx, input.RoomID)
if err != nil {
util.GetLogger(ctx).WithError(err).Error("failed querying known room")
return nil, spec.InternalServerError{}
}

logger := createInviteLogger(ctx, signedEvent, input.RoomID)
logger := createInviteLogger(ctx, input.RoomID, sender, input.InvitedUser, event.EventID())
logger.WithFields(logrus.Fields{
"room_version": signedEvent.Version(),
"room_version": event.Version(),
"room_info_exists": isKnownRoom,
}).Debug("processing incoming federation invite event")

inviteState := input.StrippedState
if len(inviteState) == 0 {
inviteState, err = GenerateStrippedState(ctx, input.RoomID, signedEvent, input.StateQuerier)
inviteState, err = GenerateStrippedState(ctx, input.RoomID, input.StateQuerier)
if err != nil {
util.GetLogger(ctx).WithError(err).Error("failed generating stripped state")
return nil, spec.InternalServerError{}
Expand All @@ -132,10 +193,10 @@ func HandleInvite(ctx context.Context, input HandleInviteInput) (PDU, error) {
}
}

err = setUnsignedFieldForInvite(signedEvent, inviteState)
err = setUnsignedFieldForInvite(event, inviteState)
if err != nil {
return nil, err
}

return signedEvent, nil
return event, nil
}
Loading