Kick users on redaction of join events

This commit is contained in:
Till Faelligen 2023-07-03 08:54:18 +02:00
parent 641bac0ce5
commit dcd28e3614
No known key found for this signature in database
GPG key ID: ACCDC9606D472758
5 changed files with 111 additions and 36 deletions

View file

@ -400,13 +400,14 @@ func (r *Inputer) processRoomEvent(
// if storing this event results in it being redacted then do so. // 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 // we do this after calculating state for this event as we may need to get power levels
var ( var (
redactedEventID string redactedEventID string
redactionEvent gomatrixserverlib.PDU redactionEvent gomatrixserverlib.PDU
redactedEvent gomatrixserverlib.PDU redactedEvent gomatrixserverlib.PDU
originalRedactedEvent gomatrixserverlib.PDU
) )
if !isRejected && !isCreateEvent { if !isRejected && !isCreateEvent {
resolver := state.NewStateResolution(r.DB, roomInfo, r.Queryer) 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 { if err != nil {
return err return err
} }
@ -486,6 +487,12 @@ func (r *Inputer) processRoomEvent(
if err != nil { if err != nil {
return fmt.Errorf("r.WriteOutputEvents (redactions): %w", err) 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. // If guest_access changed and is not can_join, kick all guest users.
@ -501,6 +508,73 @@ func (r *Inputer) processRoomEvent(
return nil 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 // handleRemoteRoomUpgrade updates published rooms and room aliases
func (r *Inputer) handleRemoteRoomUpgrade(ctx context.Context, event gomatrixserverlib.PDU) error { func (r *Inputer) handleRemoteRoomUpgrade(ctx context.Context, event gomatrixserverlib.PDU) error {
oldRoomID := event.RoomID() oldRoomID := event.RoomID()

View file

@ -647,7 +647,7 @@ func persistEvents(ctx context.Context, db storage.Database, querier api.QuerySe
resolver := state.NewStateResolution(db, roomInfo, querier) 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 { if err != nil {
logrus.WithError(err).WithField("event_id", ev.EventID()).Error("Failed to redact event") logrus.WithError(err).WithField("event_id", ev.EventID()).Error("Failed to redact event")
continue continue

View file

@ -574,7 +574,7 @@ func TestRedaction(t *testing.T) {
err = updater.Commit() err = updater.Commit()
assert.NoError(t, err) 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) assert.NoError(t, err)
if redactedEvent != nil { if redactedEvent != nil {
assert.Equal(t, ev.Redacts(), redactedEvent.EventID()) assert.Equal(t, ev.Redacts(), redactedEvent.EventID())

View file

@ -190,9 +190,7 @@ type Database interface {
GetRoomVersion(ctx context.Context, roomID string) (gomatrixserverlib.RoomVersion, error) GetRoomVersion(ctx context.Context, roomID string) (gomatrixserverlib.RoomVersion, error)
GetOrCreateEventTypeNID(ctx context.Context, eventType string) (eventTypeNID types.EventTypeNID, err error) GetOrCreateEventTypeNID(ctx context.Context, eventType string) (eventTypeNID types.EventTypeNID, err error)
GetOrCreateEventStateKeyNID(ctx context.Context, eventStateKey *string) (types.EventStateKeyNID, error) GetOrCreateEventStateKeyNID(ctx context.Context, eventStateKey *string) (types.EventStateKeyNID, error)
MaybeRedactEvent( 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)
ctx context.Context, roomInfo *types.RoomInfo, eventNID types.EventNID, event gomatrixserverlib.PDU, plResolver state.PowerLevelResolver, querier api.QuerySenderIDAPI,
) (gomatrixserverlib.PDU, gomatrixserverlib.PDU, error)
} }
type UserRoomKeys interface { type UserRoomKeys interface {
@ -249,10 +247,8 @@ type EventDatabase interface {
EventIDs(ctx context.Context, eventNIDs []types.EventNID) (map[types.EventNID]string, error) EventIDs(ctx context.Context, eventNIDs []types.EventNID) (map[types.EventNID]string, error)
EventsFromIDs(ctx context.Context, roomInfo *types.RoomInfo, eventIDs []string) ([]types.Event, 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) 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) // (nil if there was nothing to do)
MaybeRedactEvent( 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)
ctx context.Context, roomInfo *types.RoomInfo, eventNID types.EventNID, event gomatrixserverlib.PDU, plResolver state.PowerLevelResolver, querier api.QuerySenderIDAPI,
) (gomatrixserverlib.PDU, gomatrixserverlib.PDU, 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) 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)
} }

View file

@ -990,15 +990,15 @@ func extractRoomVersionFromCreateEvent(event gomatrixserverlib.PDU) (
// to cross-reference with other tables when loading. // to cross-reference with other tables when loading.
// //
// Returns the redaction event and the redacted event if this call resulted in a redaction. // Returns the redaction event and the redacted event if this call resulted in a redaction.
// nolint: gocylo
func (d *EventDatabase) MaybeRedactEvent( func (d *EventDatabase) MaybeRedactEvent(
ctx context.Context, roomInfo *types.RoomInfo, eventNID types.EventNID, event gomatrixserverlib.PDU, plResolver state.PowerLevelResolver, ctx context.Context, roomInfo *types.RoomInfo, eventNID types.EventNID, event gomatrixserverlib.PDU, plResolver state.PowerLevelResolver,
querier api.QuerySenderIDAPI, querier api.QuerySenderIDAPI,
) (gomatrixserverlib.PDU, gomatrixserverlib.PDU, error) { ) (redactionEvent, redactedEvent, originalRedactedEvent gomatrixserverlib.PDU, err error) {
var ( var (
redactionEvent, redactedEvent *types.Event redactionEv, redactedEv *types.Event
err error validated bool
validated bool ignoreRedaction bool
ignoreRedaction bool
) )
wErr := d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { 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 { switch {
case err != nil: case err != nil:
return fmt.Errorf("d.loadRedactionPair: %w", err) 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 // we've seen this redaction before or there is nothing to redact
return nil return nil
case redactedEvent.RoomID() != redactionEvent.RoomID(): case redactedEv.RoomID() != redactionEv.RoomID():
// redactions across rooms aren't allowed // redactions across rooms aren't allowed
ignoreRedaction = true ignoreRedaction = true
return nil return nil
} }
var validRoomID *spec.RoomID var validRoomID *spec.RoomID
validRoomID, err = spec.NewRoomID(redactedEvent.RoomID()) validRoomID, err = spec.NewRoomID(redactedEv.RoomID())
if err != nil { if err != nil {
return err return err
} }
sender1Domain := "" sender1Domain := ""
sender1, err1 := querier.QueryUserIDForSender(ctx, *validRoomID, redactedEvent.SenderID()) sender1, err1 := querier.QueryUserIDForSender(ctx, *validRoomID, redactedEv.SenderID())
if err1 == nil { if err1 == nil {
sender1Domain = string(sender1.Domain()) sender1Domain = string(sender1.Domain())
} }
sender2Domain := "" sender2Domain := ""
sender2, err2 := querier.QueryUserIDForSender(ctx, *validRoomID, redactionEvent.SenderID()) sender2, err2 := querier.QueryUserIDForSender(ctx, *validRoomID, redactionEv.SenderID())
if err2 == nil { if err2 == nil {
sender2Domain = string(sender2.Domain()) sender2Domain = string(sender2.Domain())
} }
var powerlevels *gomatrixserverlib.PowerLevelContent var powerlevels *gomatrixserverlib.PowerLevelContent
powerlevels, err = plResolver.Resolve(ctx, redactionEvent.EventID()) powerlevels, err = plResolver.Resolve(ctx, redactionEv.EventID())
if err != nil { if err != nil {
return err return err
} }
switch { switch {
case powerlevels.UserLevel(redactionEvent.SenderID()) >= powerlevels.Redact: case powerlevels.UserLevel(redactionEv.SenderID()) >= powerlevels.Redact:
// 1. The power level of the redaction events sender is greater than or equal to the redact level. // 1. The power level of the redaction events sender is greater than or equal to the redact level.
case sender1Domain != "" && sender2Domain != "" && sender1Domain == sender2Domain: case sender1Domain != "" && sender2Domain != "" && sender1Domain == sender2Domain:
// 2. The domain of the redaction events sender matches that of the original events sender. // 2. The domain of the redaction events sender matches that of the original events sender.
@ -1065,42 +1065,47 @@ func (d *EventDatabase) MaybeRedactEvent(
// mark the event as redacted // mark the event as redacted
if redactionsArePermanent { 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 { if err != nil {
return fmt.Errorf("redactedEvent.SetUnsignedField: %w", err) return fmt.Errorf("redactedEvent.SetUnsignedField: %w", err)
} }
// NOTSPEC: sytest relies on this unspecced field existing :( // 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 { if err != nil {
return fmt.Errorf("redactedEvent.SetUnsignedField: %w", err) return fmt.Errorf("redactedEvent.SetUnsignedField: %w", err)
} }
// overwrite the eventJSON table // 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 { if err != nil {
return fmt.Errorf("d.EventJSONTable.InsertEventJSON: %w", err) 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 { if err != nil {
return fmt.Errorf("d.RedactionsTable.MarkRedactionValidated: %w", err) return fmt.Errorf("d.RedactionsTable.MarkRedactionValidated: %w", err)
} }
// We remove the entry from the cache, as if we just "StoreRoomServerEvent", we can't be // 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. // 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 return nil
}) })
if wErr != nil { if wErr != nil {
return nil, nil, err return nil, nil, nil, err
} }
if ignoreRedaction || redactionEvent == nil || redactedEvent == nil { if ignoreRedaction || redactionEv == nil || redactedEv == nil {
return nil, nil, 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. // loadRedactionPair returns both the redaction event and the redacted event, else nil.