Handle cryptoID invites & leaves

This commit is contained in:
Devon Hudson 2023-11-06 16:54:29 -07:00
parent 7f7ac0f4fe
commit 227493cc5d
No known key found for this signature in database
GPG key ID: CD06B18E77F6A628
15 changed files with 139 additions and 86 deletions

View file

@ -15,6 +15,7 @@
package routing
import (
"encoding/json"
"net/http"
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
@ -23,11 +24,16 @@ import (
"github.com/matrix-org/util"
)
type leaveRoomCryptoIDsResponse struct {
PDU json.RawMessage `json:"pdu"`
}
func LeaveRoomByID(
req *http.Request,
device *api.Device,
rsAPI roomserverAPI.ClientRoomserverAPI,
roomID string,
cryptoIDs bool,
) util.JSONResponse {
userID, err := spec.NewUserID(device.UserID, true)
if err != nil {
@ -45,7 +51,8 @@ func LeaveRoomByID(
leaveRes := roomserverAPI.PerformLeaveResponse{}
// Ask the roomserver to perform the leave.
if err := rsAPI.PerformLeave(req.Context(), &leaveReq, &leaveRes); err != nil {
leaveEvent, err := rsAPI.PerformLeave(req.Context(), &leaveReq, &leaveRes, cryptoIDs)
if err != nil {
if leaveRes.Code != 0 {
return util.JSONResponse{
Code: leaveRes.Code,
@ -60,6 +67,8 @@ func LeaveRoomByID(
return util.JSONResponse{
Code: http.StatusOK,
JSON: struct{}{},
JSON: leaveRoomCryptoIDsResponse{
PDU: json.RawMessage(leaveEvent.JSON()),
},
}
}

View file

@ -443,7 +443,6 @@ func Setup(
return resp.(util.JSONResponse)
}, httputil.WithAllowGuests()),
).Methods(http.MethodPost, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/rooms/{roomID}/leave",
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
@ -454,7 +453,22 @@ func Setup(
return util.ErrorResponse(err)
}
return LeaveRoomByID(
req, device, rsAPI, vars["roomID"],
req, device, rsAPI, vars["roomID"], false,
)
}, httputil.WithAllowGuests()),
).Methods(http.MethodPost, http.MethodOptions)
unstableMux.Handle("/org.matrix.msc_cryptoids/rooms/{roomID}/leave",
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
logrus.Info("Processing request to /org.matrix.msc_cryptoids/rooms/{roomID}/leave")
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.ErrorResponse(err)
}
return LeaveRoomByID(
req, device, rsAPI, vars["roomID"], true,
)
}, httputil.WithAllowGuests()),
).Methods(http.MethodPost, http.MethodOptions)

View file

@ -32,6 +32,7 @@ import (
"github.com/matrix-org/dendrite/syncapi/synctypes"
userapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/fclient"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util"
"github.com/prometheus/client_golang/prometheus"
@ -171,7 +172,7 @@ func SendEvent(
}
}
e, resErr := generateSendEvent(req.Context(), r, device, roomID, eventType, stateKey, rsAPI, evTime)
e, resErr := generateSendEvent(req.Context(), r, device, roomID, eventType, stateKey, rsAPI, evTime, false)
if resErr != nil {
return *resErr
}
@ -362,7 +363,7 @@ func SendEventCryptoIDs(
}
}
e, resErr := generateSendEvent(req.Context(), r, device, roomID, eventType, stateKey, rsAPI, evTime)
e, resErr := generateSendEvent(req.Context(), r, device, roomID, eventType, stateKey, rsAPI, evTime, true)
if resErr != nil {
return *resErr
}
@ -484,6 +485,7 @@ func generateSendEvent(
roomID, eventType string, stateKey *string,
rsAPI api.ClientRoomserverAPI,
evTime time.Time,
cryptoIDs bool,
) (gomatrixserverlib.PDU, *util.JSONResponse) {
// parse the incoming http request
fullUserID, err := spec.NewUserID(device.UserID, true)
@ -531,12 +533,18 @@ func generateSendEvent(
}
}
identity, err := rsAPI.SigningIdentityFor(ctx, *validRoomID, *fullUserID)
if err != nil {
return nil, &util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{},
var identity fclient.SigningIdentity
if !cryptoIDs {
id, err := rsAPI.SigningIdentityFor(ctx, *validRoomID, *fullUserID)
if err != nil {
return nil, &util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{},
}
}
identity = id
} else {
identity.ServerName = spec.ServerName(*senderID)
}
var queryRes api.QueryLatestEventsAndStateResponse

View file

@ -228,7 +228,7 @@ func SendServerNotice(
"body": r.Content.Body,
"msgtype": r.Content.MsgType,
}
e, resErr := generateSendEvent(ctx, request, senderDevice, roomID, "m.room.message", nil, rsAPI, time.Now())
e, resErr := generateSendEvent(ctx, request, senderDevice, roomID, "m.room.message", nil, rsAPI, time.Now(), false)
if resErr != nil {
logrus.Errorf("failed to send message: %+v", resErr)
return *resErr

View file

@ -61,7 +61,7 @@ type RoomserverFederationAPI interface {
PerformMakeJoin(ctx context.Context, request *PerformJoinRequest) (gomatrixserverlib.PDU, gomatrixserverlib.RoomVersion, spec.ServerName, error)
PerformSendJoin(ctx context.Context, request *PerformSendJoinRequestCryptoIDs, response *PerformJoinResponse)
// Handle an instruction to make_leave & send_leave with a remote server.
PerformLeave(ctx context.Context, request *PerformLeaveRequest, response *PerformLeaveResponse) error
PerformLeave(ctx context.Context, request *PerformLeaveRequest, response *PerformLeaveResponse, cryptoIDs bool) 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.

View file

@ -747,6 +747,7 @@ func (r *FederationInternalAPI) PerformLeave(
ctx context.Context,
request *api.PerformLeaveRequest,
response *api.PerformLeaveResponse,
cryptoIDs bool,
) (err error) {
userID, err := spec.NewUserID(request.UserID, true)
if err != nil {

View file

@ -85,12 +85,20 @@ func BuildEvent(
}
builder := verImpl.NewEventBuilderFromProtoEvent(proto)
event, err := builder.Build(
evTime, identity.ServerName, identity.KeyID,
identity.PrivateKey,
)
if err != nil {
return nil, err
var event gomatrixserverlib.PDU
if identity.PrivateKey != nil {
event, err = builder.Build(
evTime, identity.ServerName, identity.KeyID,
identity.PrivateKey,
)
if err != nil {
return nil, err
}
} else {
event, err = builder.BuildWithoutSigning(evTime, identity.ServerName)
if err != nil {
return nil, err
}
}
return &types.HeaderedEvent{PDU: event}, nil

View file

@ -248,7 +248,7 @@ type ClientRoomserverAPI interface {
PerformJoin(ctx context.Context, req *PerformJoinRequest) (roomID string, joinedVia spec.ServerName, err error)
PerformSendJoinCryptoIDs(ctx context.Context, req *PerformJoinRequestCryptoIDs) error
PerformJoinCryptoIDs(ctx context.Context, req *PerformJoinRequest) (join gomatrixserverlib.PDU, roomID string, version gomatrixserverlib.RoomVersion, serverName spec.ServerName, err error)
PerformLeave(ctx context.Context, req *PerformLeaveRequest, res *PerformLeaveResponse) error
PerformLeave(ctx context.Context, req *PerformLeaveRequest, res *PerformLeaveResponse, cryptoIDs bool) (gomatrixserverlib.PDU, error)
PerformPublish(ctx context.Context, req *PerformPublishRequest) error
// PerformForget forgets a rooms history for a specific user
PerformForget(ctx context.Context, req *PerformForgetRequest, resp *PerformForgetResponse) error

View file

@ -262,16 +262,18 @@ func (r *RoomserverInternalAPI) PerformLeave(
ctx context.Context,
req *api.PerformLeaveRequest,
res *api.PerformLeaveResponse,
) error {
outputEvents, err := r.Leaver.PerformLeave(ctx, req, res)
cryptoIDs bool,
) (gomatrixserverlib.PDU, error) {
outputEvents, leaveEvent, err := r.Leaver.PerformLeave(ctx, req, res, cryptoIDs)
if err != nil {
sentry.CaptureException(err)
return err
return nil, err
}
if len(outputEvents) == 0 {
return nil
return leaveEvent, nil
}
return r.OutputProducer.ProduceRoomEvents(req.RoomID, outputEvents)
// TODO: cryptoIDs - what to do with this?
return leaveEvent, r.OutputProducer.ProduceRoomEvents(req.RoomID, outputEvents)
}
func (r *RoomserverInternalAPI) PerformForget(

View file

@ -179,7 +179,7 @@ func (r *Admin) PerformAdminEvacuateUser(
Leaver: *fullUserID,
}
leaveRes := &api.PerformLeaveResponse{}
outputEvents, err := r.Leaver.PerformLeave(ctx, leaveReq, leaveRes)
outputEvents, _, err := r.Leaver.PerformLeave(ctx, leaveReq, leaveRes, false)
if err != nil {
return nil, err
}

View file

@ -163,7 +163,7 @@ func (r *Inviter) PerformInvite(
isTargetLocal := r.Cfg.Matrix.IsLocalServerName(req.InviteInput.Invitee.Domain())
signingKey := req.InviteInput.PrivateKey
if info.RoomVersion == gomatrixserverlib.RoomVersionPseudoIDs {
if !cryptoIDs && info.RoomVersion == gomatrixserverlib.RoomVersionPseudoIDs {
signingKey, err = r.RSAPI.GetOrCreateUserRoomPrivateKey(ctx, req.InviteInput.Inviter, req.InviteInput.RoomID)
if err != nil {
return nil, err

View file

@ -665,15 +665,8 @@ func (r *Joiner) performJoinRoomByIDCryptoIDs(
// at this point we know we have an existing room
if inRoomRes.RoomVersion == gomatrixserverlib.RoomVersionPseudoIDs {
var pseudoIDKey ed25519.PrivateKey
pseudoIDKey, err = r.RSAPI.GetOrCreateUserRoomPrivateKey(ctx, *userID, *roomID)
if err != nil {
util.GetLogger(ctx).WithError(err).Error("GetOrCreateUserRoomPrivateKey failed")
return nil, "", "", "", err
}
mapping := &gomatrixserverlib.MXIDMapping{
UserRoomKey: spec.SenderIDFromPseudoIDKey(pseudoIDKey),
UserRoomKey: senderID,
UserID: userID.String(),
}
@ -685,9 +678,9 @@ func (r *Joiner) performJoinRoomByIDCryptoIDs(
// sign the event with the pseudo ID key
identity = fclient.SigningIdentity{
ServerName: spec.ServerName(spec.SenderIDFromPseudoIDKey(pseudoIDKey)),
ServerName: spec.ServerName(senderID),
KeyID: "ed25519:1",
PrivateKey: pseudoIDKey,
PrivateKey: nil,
}
}

View file

@ -24,6 +24,7 @@ import (
"github.com/matrix-org/dendrite/internal/eventutil"
"github.com/matrix-org/gomatrix"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/fclient"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util"
"github.com/sirupsen/logrus"
@ -52,9 +53,10 @@ func (r *Leaver) PerformLeave(
ctx context.Context,
req *api.PerformLeaveRequest,
res *api.PerformLeaveResponse,
) ([]api.OutputEvent, error) {
cryptoIDs bool,
) ([]api.OutputEvent, gomatrixserverlib.PDU, error) {
if !r.Cfg.Matrix.IsLocalServerName(req.Leaver.Domain()) {
return nil, fmt.Errorf("user %q does not belong to this homeserver", req.Leaver.String())
return nil, nil, fmt.Errorf("user %q does not belong to this homeserver", req.Leaver.String())
}
logger := logrus.WithContext(ctx).WithFields(logrus.Fields{
"room_id": req.RoomID,
@ -62,15 +64,15 @@ func (r *Leaver) PerformLeave(
})
logger.Info("User requested to leave join")
if strings.HasPrefix(req.RoomID, "!") {
output, err := r.performLeaveRoomByID(context.Background(), req, res)
output, event, err := r.performLeaveRoomByID(context.Background(), req, res, cryptoIDs)
if err != nil {
logger.WithError(err).Error("Failed to leave room")
} else {
logger.Info("User left room successfully")
}
return output, err
return output, event, err
}
return nil, fmt.Errorf("room ID %q is invalid", req.RoomID)
return nil, nil, fmt.Errorf("room ID %q is invalid", req.RoomID)
}
// nolint:gocyclo
@ -78,14 +80,15 @@ func (r *Leaver) performLeaveRoomByID(
ctx context.Context,
req *api.PerformLeaveRequest,
res *api.PerformLeaveResponse, // nolint:unparam
) ([]api.OutputEvent, error) {
cryptoIDs bool,
) ([]api.OutputEvent, gomatrixserverlib.PDU, error) {
roomID, err := spec.NewRoomID(req.RoomID)
if err != nil {
return nil, err
return nil, nil, err
}
leaver, err := r.RSAPI.QuerySenderIDForUser(ctx, *roomID, req.Leaver)
if err != nil || leaver == nil {
return nil, fmt.Errorf("leaver %s has no matching senderID in this room", req.Leaver.String())
return nil, nil, fmt.Errorf("leaver %s has no matching senderID in this room", req.Leaver.String())
}
// If there's an invite outstanding for the room then respond to
@ -94,7 +97,7 @@ func (r *Leaver) performLeaveRoomByID(
if err == nil && isInvitePending {
sender, serr := r.RSAPI.QueryUserIDForSender(ctx, *roomID, senderUser)
if serr != nil {
return nil, fmt.Errorf("failed looking up userID for sender %q: %w", senderUser, serr)
return nil, nil, fmt.Errorf("failed looking up userID for sender %q: %w", senderUser, serr)
}
var domain spec.ServerName
@ -107,7 +110,7 @@ func (r *Leaver) performLeaveRoomByID(
domain = sender.Domain()
}
if !r.Cfg.Matrix.IsLocalServerName(domain) {
return r.performFederatedRejectInvite(ctx, req, res, domain, eventID, *leaver)
return r.performFederatedRejectInvite(ctx, req, res, domain, eventID, *leaver, cryptoIDs)
}
// check that this is not a "server notice room"
accData := &userapi.QueryAccountDataResponse{}
@ -116,7 +119,7 @@ func (r *Leaver) performLeaveRoomByID(
RoomID: req.RoomID,
DataType: "m.tag",
}, accData); err != nil {
return nil, fmt.Errorf("unable to query account data: %w", err)
return nil, nil, fmt.Errorf("unable to query account data: %w", err)
}
if roomData, ok := accData.RoomAccountData[req.RoomID]; ok {
@ -124,13 +127,13 @@ func (r *Leaver) performLeaveRoomByID(
if ok {
tags := gomatrix.TagContent{}
if err = json.Unmarshal(tagData, &tags); err != nil {
return nil, fmt.Errorf("unable to unmarshal tag content")
return nil, nil, fmt.Errorf("unable to unmarshal tag content")
}
if _, ok = tags.Tags["m.server_notice"]; ok {
// mimic the returned values from Synapse
res.Message = "You cannot reject this invite"
res.Code = 403
return nil, spec.LeaveServerNoticeError()
return nil, nil, spec.LeaveServerNoticeError()
}
}
}
@ -149,22 +152,22 @@ func (r *Leaver) performLeaveRoomByID(
}
latestRes := api.QueryLatestEventsAndStateResponse{}
if err = helpers.QueryLatestEventsAndState(ctx, r.DB, r.RSAPI, &latestReq, &latestRes); err != nil {
return nil, err
return nil, nil, err
}
if !latestRes.RoomExists {
return nil, fmt.Errorf("room %q does not exist", req.RoomID)
return nil, nil, fmt.Errorf("room %q does not exist", req.RoomID)
}
// Now let's see if the user is in the room.
if len(latestRes.StateEvents) == 0 {
return nil, fmt.Errorf("user %q is not a member of room %q", req.Leaver.String(), req.RoomID)
return nil, nil, fmt.Errorf("user %q is not a member of room %q", req.Leaver.String(), req.RoomID)
}
membership, err := latestRes.StateEvents[0].Membership()
if err != nil {
return nil, fmt.Errorf("error getting membership: %w", err)
return nil, nil, fmt.Errorf("error getting membership: %w", err)
}
if membership != spec.Join && membership != spec.Invite {
return nil, fmt.Errorf("user %q is not joined to the room (membership is %q)", req.Leaver.String(), membership)
return nil, nil, fmt.Errorf("user %q is not joined to the room (membership is %q)", req.Leaver.String(), membership)
}
// Prepare the template for the leave event.
@ -177,10 +180,10 @@ func (r *Leaver) performLeaveRoomByID(
Redacts: "",
}
if err = proto.SetContent(map[string]interface{}{"membership": "leave"}); err != nil {
return nil, fmt.Errorf("eb.SetContent: %w", err)
return nil, nil, fmt.Errorf("eb.SetContent: %w", err)
}
if err = proto.SetUnsigned(struct{}{}); err != nil {
return nil, fmt.Errorf("eb.SetUnsigned: %w", err)
return nil, nil, fmt.Errorf("eb.SetUnsigned: %w", err)
}
// We know that the user is in the room at this point so let's build
@ -190,39 +193,50 @@ func (r *Leaver) performLeaveRoomByID(
validRoomID, err := spec.NewRoomID(req.RoomID)
if err != nil {
return nil, err
return nil, nil, err
}
var buildRes rsAPI.QueryLatestEventsAndStateResponse
identity, err := r.RSAPI.SigningIdentityFor(ctx, *validRoomID, req.Leaver)
if err != nil {
return nil, fmt.Errorf("SigningIdentityFor: %w", err)
var identity fclient.SigningIdentity
if !cryptoIDs {
identity, err = r.RSAPI.SigningIdentityFor(ctx, *validRoomID, req.Leaver)
if err != nil {
return nil, nil, fmt.Errorf("SigningIdentityFor: %w", err)
}
} else {
identity = fclient.SigningIdentity{
ServerName: spec.ServerName(*leaver),
KeyID: "ed25519:1",
PrivateKey: nil,
}
}
event, err := eventutil.QueryAndBuildEvent(ctx, &proto, &identity, time.Now(), r.RSAPI, &buildRes)
if err != nil {
return nil, fmt.Errorf("eventutil.QueryAndBuildEvent: %w", err)
return nil, nil, fmt.Errorf("eventutil.QueryAndBuildEvent: %w", err)
}
// Give our leave event to the roomserver input stream. The
// roomserver will process the membership change and notify
// downstream automatically.
inputReq := api.InputRoomEventsRequest{
InputRoomEvents: []api.InputRoomEvent{
{
Kind: api.KindNew,
Event: event,
Origin: req.Leaver.Domain(),
SendAsServer: string(req.Leaver.Domain()),
if !cryptoIDs {
// Give our leave event to the roomserver input stream. The
// roomserver will process the membership change and notify
// downstream automatically.
inputReq := api.InputRoomEventsRequest{
InputRoomEvents: []api.InputRoomEvent{
{
Kind: api.KindNew,
Event: event,
Origin: req.Leaver.Domain(),
SendAsServer: string(req.Leaver.Domain()),
},
},
},
}
inputRes := api.InputRoomEventsResponse{}
r.Inputer.InputRoomEvents(ctx, &inputReq, &inputRes)
if err = inputRes.Err(); err != nil {
return nil, fmt.Errorf("r.InputRoomEvents: %w", err)
}
inputRes := api.InputRoomEventsResponse{}
r.Inputer.InputRoomEvents(ctx, &inputReq, &inputRes)
if err = inputRes.Err(); err != nil {
return nil, nil, fmt.Errorf("r.InputRoomEvents: %w", err)
}
}
return nil, nil
return nil, event, nil
}
func (r *Leaver) performFederatedRejectInvite(
@ -231,7 +245,8 @@ func (r *Leaver) performFederatedRejectInvite(
res *api.PerformLeaveResponse, // nolint:unparam
inviteDomain spec.ServerName, eventID string,
leaver spec.SenderID,
) ([]api.OutputEvent, error) {
cryptoIDs bool,
) ([]api.OutputEvent, gomatrixserverlib.PDU, error) {
// Ask the federation sender to perform a federated leave for us.
leaveReq := fsAPI.PerformLeaveRequest{
RoomID: req.RoomID,
@ -239,7 +254,7 @@ func (r *Leaver) performFederatedRejectInvite(
ServerNames: []spec.ServerName{inviteDomain},
}
leaveRes := fsAPI.PerformLeaveResponse{}
if err := r.FSAPI.PerformLeave(ctx, &leaveReq, &leaveRes); err != nil {
if err := r.FSAPI.PerformLeave(ctx, &leaveReq, &leaveRes, cryptoIDs); err != nil {
// failures in PerformLeave should NEVER stop us from telling other components like the
// sync API that the invite was withdrawn. Otherwise we can end up with stuck invites.
util.GetLogger(ctx).WithError(err).Errorf("failed to PerformLeave, still retiring invite event")
@ -279,5 +294,5 @@ func (r *Leaver) performFederatedRejectInvite(
TargetSenderID: leaver,
},
},
}, nil
}, nil, nil
}

View file

@ -532,6 +532,7 @@ type InviteResponse struct {
InviteState struct {
Events []json.RawMessage `json:"events"`
} `json:"invite_state"`
OneTimePseudoID string `json:"one_time_pseudoid,omitempty"`
}
// NewInviteResponse creates an empty response with initialised arrays.
@ -539,6 +540,8 @@ func NewInviteResponse(ctx context.Context, rsAPI api.QuerySenderIDAPI, event *t
res := InviteResponse{}
res.InviteState.Events = []json.RawMessage{}
res.OneTimePseudoID = *event.PDU.StateKey()
// First see if there's invite_room_state in the unsigned key of the invite.
// If there is then unmarshal it into the response. This will contain the
// partial room state such as join rules, room name etc.

View file

@ -858,15 +858,15 @@ type Ed25519Key struct {
}
func (a *UserInternalAPI) ClaimOneTimePseudoID(ctx context.Context, roomID spec.RoomID, userID spec.UserID) (spec.SenderID, error) {
pseudoIDs, err := a.KeyDatabase.ClaimOneTimePseudoID(ctx, userID, "ed25519")
pseudoID, err := a.KeyDatabase.ClaimOneTimePseudoID(ctx, userID, "ed25519")
if err != nil {
return "", err
}
logrus.Infof("Claimed one time pseuodID: %v", pseudoIDs)
logrus.Infof("Claimed one time pseuodID: %s", pseudoID)
if pseudoIDs != nil {
for key, value := range pseudoIDs.KeyJSON {
if pseudoID != nil {
for key, value := range pseudoID.KeyJSON {
keyParts := strings.Split(key, ":")
if keyParts[0] == "ed25519" {
var key_bytes Ed25519Key