From dcd28e36145abeffecf794c90357376768b74e02 Mon Sep 17 00:00:00 2001 From: Till Faelligen <2353100+S7evinK@users.noreply.github.com> Date: Mon, 3 Jul 2023 08:54:18 +0200 Subject: [PATCH] Kick users on redaction of join events --- roomserver/internal/input/input_events.go | 82 ++++++++++++++++++- .../internal/perform/perform_backfill.go | 2 +- roomserver/roomserver_test.go | 2 +- roomserver/storage/interface.go | 10 +-- roomserver/storage/shared/storage.go | 51 ++++++------ 5 files changed, 111 insertions(+), 36 deletions(-) diff --git a/roomserver/internal/input/input_events.go b/roomserver/internal/input/input_events.go index db3c95502..0d79a54db 100644 --- a/roomserver/internal/input/input_events.go +++ b/roomserver/internal/input/input_events.go @@ -400,13 +400,14 @@ func (r *Inputer) processRoomEvent( // if storing this event results in it being redacted then do so. // we do this after calculating state for this event as we may need to get power levels var ( - redactedEventID string - redactionEvent gomatrixserverlib.PDU - redactedEvent gomatrixserverlib.PDU + redactedEventID string + redactionEvent gomatrixserverlib.PDU + redactedEvent gomatrixserverlib.PDU + originalRedactedEvent gomatrixserverlib.PDU ) if !isRejected && !isCreateEvent { resolver := state.NewStateResolution(r.DB, roomInfo, r.Queryer) - redactionEvent, redactedEvent, err = r.DB.MaybeRedactEvent(ctx, roomInfo, eventNID, event, &resolver, r.Queryer) + redactionEvent, redactedEvent, originalRedactedEvent, err = r.DB.MaybeRedactEvent(ctx, roomInfo, eventNID, event, &resolver, r.Queryer) if err != nil { return err } @@ -486,6 +487,12 @@ func (r *Inputer) processRoomEvent( if err != nil { return fmt.Errorf("r.WriteOutputEvents (redactions): %w", err) } + // if we're in a pseudoID room, and we redacted a m.room.member event, also leave/kick the user + if event.Version() == gomatrixserverlib.RoomVersionPseudoIDs && redactedEvent.Type() == spec.MRoomMember { + if err = r.leavePseudoIDRoom(ctx, *validRoomID, originalRedactedEvent, redactionEvent); err != nil { + logrus.WithError(err).Error("failed to leave user after membership event redaction") + } + } } // If guest_access changed and is not can_join, kick all guest users. @@ -501,6 +508,73 @@ func (r *Inputer) processRoomEvent( return nil } +// leavePseudoIDRoom leaves/kicks a user in the event of a membership event redaction. +// TODO: This doesn't play well with users re-joining rooms, as in this case we have multiple join events with a mxid_mapping. +func (r *Inputer) leavePseudoIDRoom(ctx context.Context, roomID spec.RoomID, originalRedactedEvent, redactionEvent gomatrixserverlib.PDU) error { + var memberContent gomatrixserverlib.MemberContent + if err := json.Unmarshal(originalRedactedEvent.Content(), &memberContent); err != nil { + return err + } + if memberContent.Membership != spec.Join { + return nil + } + // no mxid_mapping, nothing to do + if memberContent.MXIDMapping == nil { + return nil + } + + userID, err := r.Queryer.QueryUserIDForSender(ctx, roomID, redactionEvent.SenderID()) + if err != nil { + return err + } + + // We can only create the leave event on servers the redaction originated on. + // We are going to receive the leave event anyway. + if !r.Cfg.Matrix.IsLocalServerName(userID.Domain()) { + return nil + } + + // check that the redacted event is the _current_ membership event + stateKey := originalRedactedEvent.StateKey() + + signingIdentity, err := r.SigningIdentity(ctx, roomID, *userID) + if err != nil { + return err + } + + fledglingEvent := &gomatrixserverlib.ProtoEvent{ + RoomID: originalRedactedEvent.RoomID(), + Type: spec.MRoomMember, + StateKey: stateKey, + SenderID: string(redactionEvent.SenderID()), + } + + if fledglingEvent.Content, err = json.Marshal(gomatrixserverlib.MemberContent{ + Membership: spec.Leave, + }); err != nil { + return err + } + + event, err := eventutil.QueryAndBuildEvent(ctx, fledglingEvent, &signingIdentity, time.Now(), r.Queryer, nil) + if err != nil { + return err + } + + inputReq := &api.InputRoomEventsRequest{ + InputRoomEvents: []api.InputRoomEvent{{ + Kind: api.KindNew, + Event: event, + Origin: userID.Domain(), + SendAsServer: string(userID.Domain()), + }}, + Asynchronous: true, // Needs to be async, as we otherwise create a deadlock + } + inputRes := &api.InputRoomEventsResponse{} + r.InputRoomEvents(ctx, inputReq, inputRes) + + return nil +} + // handleRemoteRoomUpgrade updates published rooms and room aliases func (r *Inputer) handleRemoteRoomUpgrade(ctx context.Context, event gomatrixserverlib.PDU) error { oldRoomID := event.RoomID() diff --git a/roomserver/internal/perform/perform_backfill.go b/roomserver/internal/perform/perform_backfill.go index 33200e819..ef56e0cee 100644 --- a/roomserver/internal/perform/perform_backfill.go +++ b/roomserver/internal/perform/perform_backfill.go @@ -647,7 +647,7 @@ func persistEvents(ctx context.Context, db storage.Database, querier api.QuerySe resolver := state.NewStateResolution(db, roomInfo, querier) - _, redactedEvent, err := db.MaybeRedactEvent(ctx, roomInfo, eventNID, ev, &resolver, querier) + _, redactedEvent, _, err := db.MaybeRedactEvent(ctx, roomInfo, eventNID, ev, &resolver, querier) if err != nil { logrus.WithError(err).WithField("event_id", ev.EventID()).Error("Failed to redact event") continue diff --git a/roomserver/roomserver_test.go b/roomserver/roomserver_test.go index 76b21ad23..3aed30340 100644 --- a/roomserver/roomserver_test.go +++ b/roomserver/roomserver_test.go @@ -574,7 +574,7 @@ func TestRedaction(t *testing.T) { err = updater.Commit() assert.NoError(t, err) - _, redactedEvent, err := db.MaybeRedactEvent(ctx, roomInfo, eventNID, ev.PDU, &plResolver, &FakeQuerier{}) + _, redactedEvent, _, err := db.MaybeRedactEvent(ctx, roomInfo, eventNID, ev.PDU, &plResolver, &FakeQuerier{}) assert.NoError(t, err) if redactedEvent != nil { assert.Equal(t, ev.Redacts(), redactedEvent.EventID()) diff --git a/roomserver/storage/interface.go b/roomserver/storage/interface.go index e9b4609ec..93a491ecc 100644 --- a/roomserver/storage/interface.go +++ b/roomserver/storage/interface.go @@ -190,9 +190,7 @@ type Database interface { GetRoomVersion(ctx context.Context, roomID string) (gomatrixserverlib.RoomVersion, error) GetOrCreateEventTypeNID(ctx context.Context, eventType string) (eventTypeNID types.EventTypeNID, err error) GetOrCreateEventStateKeyNID(ctx context.Context, eventStateKey *string) (types.EventStateKeyNID, error) - MaybeRedactEvent( - ctx context.Context, roomInfo *types.RoomInfo, eventNID types.EventNID, event gomatrixserverlib.PDU, plResolver state.PowerLevelResolver, querier api.QuerySenderIDAPI, - ) (gomatrixserverlib.PDU, gomatrixserverlib.PDU, error) + MaybeRedactEvent(ctx context.Context, roomInfo *types.RoomInfo, eventNID types.EventNID, event gomatrixserverlib.PDU, plResolver state.PowerLevelResolver, querier api.QuerySenderIDAPI) (gomatrixserverlib.PDU, gomatrixserverlib.PDU, gomatrixserverlib.PDU, error) } type UserRoomKeys interface { @@ -249,10 +247,8 @@ type EventDatabase interface { EventIDs(ctx context.Context, eventNIDs []types.EventNID) (map[types.EventNID]string, error) EventsFromIDs(ctx context.Context, roomInfo *types.RoomInfo, eventIDs []string) ([]types.Event, error) Events(ctx context.Context, roomVersion gomatrixserverlib.RoomVersion, eventNIDs []types.EventNID) ([]types.Event, error) - // MaybeRedactEvent returns the redaction event and the redacted event if this call resulted in a redaction, else an error + // MaybeRedactEvent returns the redaction event, the redacted event and the event before redaction if this call resulted in a redaction, else an error // (nil if there was nothing to do) - MaybeRedactEvent( - ctx context.Context, roomInfo *types.RoomInfo, eventNID types.EventNID, event gomatrixserverlib.PDU, plResolver state.PowerLevelResolver, querier api.QuerySenderIDAPI, - ) (gomatrixserverlib.PDU, gomatrixserverlib.PDU, error) + MaybeRedactEvent(ctx context.Context, roomInfo *types.RoomInfo, eventNID types.EventNID, event gomatrixserverlib.PDU, plResolver state.PowerLevelResolver, querier api.QuerySenderIDAPI) (redactionEvent, redactedEvent, originalRedactedEvent gomatrixserverlib.PDU, err error) StoreEvent(ctx context.Context, event gomatrixserverlib.PDU, roomInfo *types.RoomInfo, eventTypeNID types.EventTypeNID, eventStateKeyNID types.EventStateKeyNID, authEventNIDs []types.EventNID, isRejected bool) (types.EventNID, types.StateAtEvent, error) } diff --git a/roomserver/storage/shared/storage.go b/roomserver/storage/shared/storage.go index 92c9751d3..e0f421afe 100644 --- a/roomserver/storage/shared/storage.go +++ b/roomserver/storage/shared/storage.go @@ -990,15 +990,15 @@ func extractRoomVersionFromCreateEvent(event gomatrixserverlib.PDU) ( // to cross-reference with other tables when loading. // // Returns the redaction event and the redacted event if this call resulted in a redaction. +// nolint: gocylo func (d *EventDatabase) MaybeRedactEvent( ctx context.Context, roomInfo *types.RoomInfo, eventNID types.EventNID, event gomatrixserverlib.PDU, plResolver state.PowerLevelResolver, querier api.QuerySenderIDAPI, -) (gomatrixserverlib.PDU, gomatrixserverlib.PDU, error) { +) (redactionEvent, redactedEvent, originalRedactedEvent gomatrixserverlib.PDU, err error) { var ( - redactionEvent, redactedEvent *types.Event - err error - validated bool - ignoreRedaction bool + redactionEv, redactedEv *types.Event + validated bool + ignoreRedaction bool ) wErr := d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { @@ -1019,42 +1019,42 @@ func (d *EventDatabase) MaybeRedactEvent( } } - redactionEvent, redactedEvent, validated, err = d.loadRedactionPair(ctx, txn, roomInfo, eventNID, event) + redactionEv, redactedEv, validated, err = d.loadRedactionPair(ctx, txn, roomInfo, eventNID, event) switch { case err != nil: return fmt.Errorf("d.loadRedactionPair: %w", err) - case validated || redactedEvent == nil || redactionEvent == nil: + case validated || redactedEv == nil || redactionEv == nil: // we've seen this redaction before or there is nothing to redact return nil - case redactedEvent.RoomID() != redactionEvent.RoomID(): + case redactedEv.RoomID() != redactionEv.RoomID(): // redactions across rooms aren't allowed ignoreRedaction = true return nil } var validRoomID *spec.RoomID - validRoomID, err = spec.NewRoomID(redactedEvent.RoomID()) + validRoomID, err = spec.NewRoomID(redactedEv.RoomID()) if err != nil { return err } sender1Domain := "" - sender1, err1 := querier.QueryUserIDForSender(ctx, *validRoomID, redactedEvent.SenderID()) + sender1, err1 := querier.QueryUserIDForSender(ctx, *validRoomID, redactedEv.SenderID()) if err1 == nil { sender1Domain = string(sender1.Domain()) } sender2Domain := "" - sender2, err2 := querier.QueryUserIDForSender(ctx, *validRoomID, redactionEvent.SenderID()) + sender2, err2 := querier.QueryUserIDForSender(ctx, *validRoomID, redactionEv.SenderID()) if err2 == nil { sender2Domain = string(sender2.Domain()) } var powerlevels *gomatrixserverlib.PowerLevelContent - powerlevels, err = plResolver.Resolve(ctx, redactionEvent.EventID()) + powerlevels, err = plResolver.Resolve(ctx, redactionEv.EventID()) if err != nil { return err } switch { - case powerlevels.UserLevel(redactionEvent.SenderID()) >= powerlevels.Redact: + case powerlevels.UserLevel(redactionEv.SenderID()) >= powerlevels.Redact: // 1. The power level of the redaction event’s sender is greater than or equal to the redact level. case sender1Domain != "" && sender2Domain != "" && sender1Domain == sender2Domain: // 2. The domain of the redaction event’s sender matches that of the original event’s sender. @@ -1065,42 +1065,47 @@ func (d *EventDatabase) MaybeRedactEvent( // mark the event as redacted if redactionsArePermanent { - redactedEvent.Redact() + originalRedactedEvent, err = gomatrixserverlib.MustGetRoomVersion(redactedEv.Version()). + NewEventFromTrustedJSON(redactedEv.JSON(), false) + if err != nil { + return err + } + redactedEv.Redact() } - err = redactedEvent.SetUnsignedField("redacted_because", redactionEvent) + err = redactedEv.SetUnsignedField("redacted_because", redactionEv) if err != nil { return fmt.Errorf("redactedEvent.SetUnsignedField: %w", err) } // NOTSPEC: sytest relies on this unspecced field existing :( - err = redactedEvent.SetUnsignedField("redacted_by", redactionEvent.EventID()) + err = redactedEv.SetUnsignedField("redacted_by", redactionEv.EventID()) if err != nil { return fmt.Errorf("redactedEvent.SetUnsignedField: %w", err) } // overwrite the eventJSON table - err = d.EventJSONTable.InsertEventJSON(ctx, txn, redactedEvent.EventNID, redactedEvent.JSON()) + err = d.EventJSONTable.InsertEventJSON(ctx, txn, redactedEv.EventNID, redactedEv.JSON()) if err != nil { return fmt.Errorf("d.EventJSONTable.InsertEventJSON: %w", err) } - err = d.RedactionsTable.MarkRedactionValidated(ctx, txn, redactionEvent.EventID(), true) + err = d.RedactionsTable.MarkRedactionValidated(ctx, txn, redactionEv.EventID(), true) if err != nil { return fmt.Errorf("d.RedactionsTable.MarkRedactionValidated: %w", err) } // We remove the entry from the cache, as if we just "StoreRoomServerEvent", we can't be // certain that the cached entry actually is updated, since ristretto is eventual-persistent. - d.Cache.InvalidateRoomServerEvent(redactedEvent.EventNID) + d.Cache.InvalidateRoomServerEvent(redactedEv.EventNID) return nil }) if wErr != nil { - return nil, nil, err + return nil, nil, nil, err } - if ignoreRedaction || redactionEvent == nil || redactedEvent == nil { - return nil, nil, nil + if ignoreRedaction || redactionEv == nil || redactedEv == nil { + return nil, nil, nil, nil } - return redactionEvent.PDU, redactedEvent.PDU, nil + return redactionEv.PDU, redactedEv.PDU, originalRedactedEvent, nil } // loadRedactionPair returns both the redaction event and the redacted event, else nil.