Skip to content
This repository has been archived by the owner on Nov 14, 2024. It is now read-only.

Commit

Permalink
Add pseudoID compatibility to invites
Browse files Browse the repository at this point in the history
  • Loading branch information
devonh committed Jun 28, 2023
1 parent 0e1b81b commit 909a1a5
Show file tree
Hide file tree
Showing 9 changed files with 282 additions and 155 deletions.
48 changes: 40 additions & 8 deletions clientapi/routing/membership.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,22 +338,54 @@ func sendInvite(
rsAPI roomserverAPI.ClientRoomserverAPI,
asAPI appserviceAPI.AppServiceInternalAPI, evTime time.Time,
) (util.JSONResponse, error) {
event, err := buildMembershipEvent(
ctx, userID, reason, profileAPI, device, spec.Invite,
roomID, false, cfg, evTime, rsAPI, asAPI,
)
validRoomID, err := spec.NewRoomID(roomID)
if err != nil {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.InvalidParam("RoomID is invalid"),
}, err
}
inviter, err := spec.NewUserID(device.UserID, true)
if err != nil {
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{},
}, err
}
invitee, err := spec.NewUserID(userID, true)
if err != nil {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.InvalidParam("UserID is invalid"),
}, err
}
profile, err := loadProfile(ctx, userID, cfg, profileAPI, asAPI)
if err != nil {
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{},
}, err
}
identity, err := cfg.Matrix.SigningIdentityFor(device.UserDomain())
if err != nil {
util.GetLogger(ctx).WithError(err).Error("buildMembershipEvent failed")
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{},
}, err
}

err = rsAPI.PerformInvite(ctx, &api.PerformInviteRequest{
Event: event,
InviteInput: roomserverAPI.InviteInput{
RoomID: *validRoomID,
Inviter: *inviter,
Invitee: *invitee,
DisplayName: profile.DisplayName,
AvatarURL: profile.AvatarURL,
Reason: reason,
IsDirect: false,
KeyID: identity.KeyID,
PrivateKey: identity.PrivateKey,
},
InviteRoomState: nil, // ask the roomserver to draw up invite room state for us
RoomVersion: event.Version(),
SendAsServer: string(device.UserDomain()),
})

Expand Down
2 changes: 2 additions & 0 deletions federationapi/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ type RoomserverFederationAPI interface {
PerformLeave(ctx context.Context, request *PerformLeaveRequest, response *PerformLeaveResponse) error
// Handle sending an invite to a remote server.
SendInvite(ctx context.Context, event gomatrixserverlib.PDU, strippedState []gomatrixserverlib.InviteStrippedState) (gomatrixserverlib.PDU, error)
// Handle sending an invite to a remote server.
SendInviteV3(ctx context.Context, event gomatrixserverlib.ProtoEvent, invitee spec.UserID, version gomatrixserverlib.RoomVersion, strippedState []gomatrixserverlib.InviteStrippedState) (gomatrixserverlib.PDU, error)
// Handle an instruction to peek a room on a remote server.
PerformOutboundPeek(ctx context.Context, request *PerformOutboundPeekRequest, response *PerformOutboundPeekResponse) error
// Query the server names of the joined hosts in a room.
Expand Down
55 changes: 55 additions & 0 deletions federationapi/internal/perform.go
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,61 @@ func (r *FederationInternalAPI) SendInvite(
return inviteEvent, nil
}

// SendInviteV3 implements api.FederationInternalAPI
func (r *FederationInternalAPI) SendInviteV3(
ctx context.Context,
event gomatrixserverlib.ProtoEvent,
invitee spec.UserID,
version gomatrixserverlib.RoomVersion,
strippedState []gomatrixserverlib.InviteStrippedState,
) (gomatrixserverlib.PDU, error) {
validRoomID, err := spec.NewRoomID(event.RoomID)
if err != nil {
return nil, err
}
inviter, err := r.rsAPI.QueryUserIDForSender(ctx, *validRoomID, spec.SenderID(event.SenderID))
if err != nil {
return nil, err
}

if event.StateKey == nil {
return nil, errors.New("invite must be a state event")
}

// TODO (devon): This should be allowed via a relay. Currently only transactions
// can be sent to relays. Would need to extend relays to handle invites.
if !r.shouldAttemptDirectFederation(invitee.Domain()) {
return nil, fmt.Errorf("relay servers have no meaningful response for invite.")
}

logrus.WithFields(logrus.Fields{
"user_id": *event.StateKey,
"room_id": event.RoomID,
"room_version": version,
"destination": invitee.Domain(),
}).Info("Sending invite")

inviteReq, err := fclient.NewInviteV3Request(event, version, strippedState)
if err != nil {
return nil, fmt.Errorf("gomatrixserverlib.NewInviteV3Request: %w", err)
}

inviteRes, err := r.federation.SendInviteV3(ctx, inviter.Domain(), invitee.Domain(), inviteReq, invitee)
if err != nil {
return nil, fmt.Errorf("r.federation.SendInviteV3: failed to send invite: %w", err)
}
verImpl, err := gomatrixserverlib.GetRoomVersion(version)
if err != nil {
return nil, err
}

inviteEvent, err := verImpl.NewEventFromUntrustedJSON(inviteRes.Event)
if err != nil {
return nil, fmt.Errorf("r.federation.SendInviteV3 failed to decode event response: %w", err)
}
return inviteEvent, nil
}

// PerformServersAlive implements api.FederationInternalAPI
func (r *FederationInternalAPI) PerformBroadcastEDU(
ctx context.Context,
Expand Down
120 changes: 95 additions & 25 deletions federationapi/routing/invite.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package routing

import (
"context"
"crypto/ed25519"
"encoding/json"
"fmt"
"net/http"
Expand All @@ -29,6 +30,91 @@ import (
"github.com/matrix-org/util"
)

// InviteV3 implements /_matrix/federation/v2/invite/{roomID}/{userID}
func InviteV3(
httpReq *http.Request,
request *fclient.FederationRequest,
roomID spec.RoomID,
invitedUser spec.UserID,
cfg *config.FederationAPI,
rsAPI api.FederationRoomserverAPI,
keys gomatrixserverlib.JSONVerifier,
) util.JSONResponse {
inviteReq := fclient.InviteV3Request{}
err := json.Unmarshal(request.Content(), &inviteReq)
switch e := err.(type) {
case gomatrixserverlib.UnsupportedRoomVersionError:
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.UnsupportedRoomVersion(
fmt.Sprintf("Room version %q is not supported by this server.", e.Version),
),
}
case gomatrixserverlib.BadJSONError:
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.BadJSON(err.Error()),
}
case nil:
if !cfg.Matrix.IsLocalServerName(invitedUser.Domain()) {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.InvalidParam("The invited user domain does not belong to this server"),
}
}

input := gomatrixserverlib.HandleInviteV3Input{
HandleInviteInput: gomatrixserverlib.HandleInviteInput{
RoomVersion: inviteReq.RoomVersion(),
RoomID: roomID,
InvitedUser: invitedUser,
KeyID: cfg.Matrix.KeyID,
PrivateKey: cfg.Matrix.PrivateKey,
Verifier: keys,
RoomQuerier: rsAPI,
MembershipQuerier: &api.MembershipQuerier{Roomserver: rsAPI},
StateQuerier: rsAPI.StateQuerier(),
InviteEvent: nil,
StrippedState: inviteReq.InviteRoomState(),
UserIDQuerier: func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
return rsAPI.QueryUserIDForSender(httpReq.Context(), roomID, senderID)
},
},
Origin: request.Origin(),
InviteProtoEvent: inviteReq.Event(),
SenderIDQuerier: func(roomID spec.RoomID, userID spec.UserID) (spec.SenderID, error) {
return rsAPI.QuerySenderIDForUser(httpReq.Context(), roomID, userID)
},
SenderIDCreator: func(ctx context.Context, userID spec.UserID, roomID spec.RoomID, roomVersion string) (spec.SenderID, ed25519.PrivateKey, error) {
// assign a roomNID, otherwise we can't create a private key for the user
_, nidErr := rsAPI.AssignRoomNID(ctx, roomID, gomatrixserverlib.RoomVersion(roomVersion))
if nidErr != nil {
return "", nil, nidErr
}
key, keyErr := rsAPI.GetOrCreateUserRoomPrivateKey(ctx, userID, roomID)
if keyErr != nil {
return "", nil, keyErr
}

return spec.SenderIDFromPseudoIDKey(key), key, nil
},
}
event, jsonErr := handleInviteV3(httpReq.Context(), input, rsAPI)
if jsonErr != nil {
return *jsonErr
}
return util.JSONResponse{
Code: http.StatusOK,
JSON: fclient.RespInviteV2{Event: event.JSON()},
}
default:
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.NotJSON("The request body could not be decoded into an invite request. " + err.Error()),
}
}
}

// InviteV2 implements /_matrix/federation/v2/invite/{roomID}/{eventID}
func InviteV2(
httpReq *http.Request,
Expand Down Expand Up @@ -204,6 +290,15 @@ func InviteV1(

func handleInvite(ctx context.Context, input gomatrixserverlib.HandleInviteInput, rsAPI api.FederationRoomserverAPI) (gomatrixserverlib.PDU, *util.JSONResponse) {
inviteEvent, err := gomatrixserverlib.HandleInvite(ctx, input)
return handleInviteResult(ctx, inviteEvent, err, rsAPI)
}

func handleInviteV3(ctx context.Context, input gomatrixserverlib.HandleInviteV3Input, rsAPI api.FederationRoomserverAPI) (gomatrixserverlib.PDU, *util.JSONResponse) {
inviteEvent, err := gomatrixserverlib.HandleInviteV3(ctx, input)
return handleInviteResult(ctx, inviteEvent, err, rsAPI)
}

func handleInviteResult(ctx context.Context, inviteEvent gomatrixserverlib.PDU, err error, rsAPI api.FederationRoomserverAPI) (gomatrixserverlib.PDU, *util.JSONResponse) {
switch e := err.(type) {
case nil:
case spec.InternalServerError:
Expand Down Expand Up @@ -245,30 +340,5 @@ func handleInvite(ctx context.Context, input gomatrixserverlib.HandleInviteInput
}
}
return inviteEvent, nil
}

// MakeInvite implements /_matrix/federation/v2/make_invite/{roomID}/{userID}
func MakeInvite(
httpReq *http.Request,
request *fclient.FederationRequest,
roomID spec.RoomID,
userID spec.UserID,
cfg *config.FederationAPI,
rsAPI api.FederationRoomserverAPI,
keys gomatrixserverlib.JSONVerifier,
) util.JSONResponse {
return util.JSONResponse{}
}

// SendInvite implements /_matrix/federation/v2/send_invite/{roomID}/{eventID}
func SendInvite(
httpReq *http.Request,
request *fclient.FederationRequest,
roomID spec.RoomID,
eventID string,
cfg *config.FederationAPI,
rsAPI api.FederationRoomserverAPI,
keys gomatrixserverlib.JSONVerifier,
) util.JSONResponse {
return util.JSONResponse{}
}
3 changes: 0 additions & 3 deletions federationapi/routing/join.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,6 @@ func MakeJoin(
}

// SendJoin implements the /send_join API
// The make-join send-join dance makes much more sense as a single
// flow so the cyclomatic complexity is high:
// nolint:gocyclo
func SendJoin(
httpReq *http.Request,
request *fclient.FederationRequest,
Expand Down
31 changes: 4 additions & 27 deletions federationapi/routing/routing.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func Setup(
v2keysmux := keyMux.PathPrefix("/v2").Subrouter()
v1fedmux := fedMux.PathPrefix("/v1").Subrouter()
v2fedmux := fedMux.PathPrefix("/v2").Subrouter()
v3fedmux := fedMux.PathPrefix("/v3").Subrouter()

wakeup := &FederationWakeups{
FsAPI: fsAPI,
Expand Down Expand Up @@ -191,8 +192,8 @@ func Setup(
},
)).Methods(http.MethodPut, http.MethodOptions)

v2fedmux.Handle("/make_invite/{roomID}/{userID}", MakeFedAPI(
"federation_make_invite", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
v3fedmux.Handle("/invite/{roomID}/{userID}", MakeFedAPI(
"federation_invite", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
func(httpReq *http.Request, request *fclient.FederationRequest, vars map[string]string) util.JSONResponse {
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
return util.JSONResponse{
Expand All @@ -215,35 +216,11 @@ func Setup(
JSON: spec.InvalidParam("Invalid RoomID"),
}
}
return MakeInvite(
return InviteV3(
httpReq, request, *roomID, *userID,
cfg, rsAPI, keys,
)
},
)).Methods(http.MethodGet, http.MethodOptions)

v2fedmux.Handle("/send_invite/{roomID}/{eventID}", MakeFedAPI(
"federation_send_invite", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
func(httpReq *http.Request, request *fclient.FederationRequest, vars map[string]string) util.JSONResponse {
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
return util.JSONResponse{
Code: http.StatusForbidden,
JSON: spec.Forbidden("Forbidden by server ACLs"),
}
}

roomID, err := spec.NewRoomID(vars["roomID"])
if err != nil {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.InvalidParam("Invalid RoomID"),
}
}
return SendInvite(
httpReq, request, *roomID, vars["eventID"],
cfg, rsAPI, keys,
)
},
)).Methods(http.MethodPut, http.MethodOptions)

v1fedmux.Handle("/3pid/onbind", httputil.MakeExternalAPI("3pid_onbind",
Expand Down
15 changes: 13 additions & 2 deletions roomserver/api/perform.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,20 @@ type PerformLeaveResponse struct {
Message interface{} `json:"message,omitempty"`
}

type InviteInput struct {
RoomID spec.RoomID
Inviter spec.UserID
Invitee spec.UserID
DisplayName string
AvatarURL string
Reason string
IsDirect bool
KeyID gomatrixserverlib.KeyID
PrivateKey ed25519.PrivateKey
}

type PerformInviteRequest struct {
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
Event *types.HeaderedEvent `json:"event"`
InviteInput InviteInput
InviteRoomState []gomatrixserverlib.InviteStrippedState `json:"invite_room_state"`
SendAsServer string `json:"send_as_server"`
TransactionID *TransactionID `json:"transaction_id"`
Expand Down
Loading

0 comments on commit 909a1a5

Please sign in to comment.