mirror of
https://github.com/hoernschen/dendrite.git
synced 2025-07-30 04:52:46 +00:00
Emit redacted_event from the roomserver when redactions are validated (#1186)
* Emit redacted_event from the roomserver when redactions are validated - Consume them in the currentstateserver and act accordingly. - Add integration test for the roomserver to check that injecting `m.room.redaction` events result in `redacted_event` being emitted. * Linting * Ignore events that redact themselves
This commit is contained in:
parent
d7a8bbff72
commit
99ea1f9b48
18 changed files with 406 additions and 107 deletions
|
@ -63,8 +63,10 @@ type Database interface {
|
|||
SnapshotNIDFromEventID(ctx context.Context, eventID string) (types.StateSnapshotNID, error)
|
||||
// Look up a room version from the room NID.
|
||||
GetRoomVersionForRoomNID(ctx context.Context, roomNID types.RoomNID) (gomatrixserverlib.RoomVersion, error)
|
||||
// Stores a matrix room event in the database
|
||||
StoreEvent(ctx context.Context, event gomatrixserverlib.Event, txnAndSessionID *api.TransactionID, authEventNIDs []types.EventNID) (types.RoomNID, types.StateAtEvent, error)
|
||||
// Stores a matrix room event in the database. Returns the room NID, the state snapshot and the redacted event ID if any, or an error.
|
||||
StoreEvent(
|
||||
ctx context.Context, event gomatrixserverlib.Event, txnAndSessionID *api.TransactionID, authEventNIDs []types.EventNID,
|
||||
) (types.RoomNID, types.StateAtEvent, *gomatrixserverlib.Event, string, error)
|
||||
// Look up the state entries for a list of string event IDs
|
||||
// Returns an error if the there is an error talking to the database
|
||||
// Returns a types.MissingEventError if the event IDs aren't in the database.
|
||||
|
|
|
@ -41,11 +41,11 @@ const insertRedactionSQL = "" +
|
|||
"INSERT INTO roomserver_redactions (redaction_event_id, redacts_event_id, validated)" +
|
||||
" VALUES ($1, $2, $3)"
|
||||
|
||||
const selectRedactedEventSQL = "" +
|
||||
const selectRedactionInfoByRedactionEventIDSQL = "" +
|
||||
"SELECT redaction_event_id, redacts_event_id, validated FROM roomserver_redactions" +
|
||||
" WHERE redaction_event_id = $1"
|
||||
|
||||
const selectRedactionEventSQL = "" +
|
||||
const selectRedactionInfoByEventBeingRedactedSQL = "" +
|
||||
"SELECT redaction_event_id, redacts_event_id, validated FROM roomserver_redactions" +
|
||||
" WHERE redacts_event_id = $1"
|
||||
|
||||
|
@ -53,10 +53,10 @@ const markRedactionValidatedSQL = "" +
|
|||
" UPDATE roomserver_redactions SET validated = $2 WHERE redaction_event_id = $1"
|
||||
|
||||
type redactionStatements struct {
|
||||
insertRedactionStmt *sql.Stmt
|
||||
selectRedactedEventStmt *sql.Stmt
|
||||
selectRedactionEventStmt *sql.Stmt
|
||||
markRedactionValidatedStmt *sql.Stmt
|
||||
insertRedactionStmt *sql.Stmt
|
||||
selectRedactionInfoByRedactionEventIDStmt *sql.Stmt
|
||||
selectRedactionInfoByEventBeingRedactedStmt *sql.Stmt
|
||||
markRedactionValidatedStmt *sql.Stmt
|
||||
}
|
||||
|
||||
func NewPostgresRedactionsTable(db *sql.DB) (tables.Redactions, error) {
|
||||
|
@ -68,8 +68,8 @@ func NewPostgresRedactionsTable(db *sql.DB) (tables.Redactions, error) {
|
|||
|
||||
return s, shared.StatementList{
|
||||
{&s.insertRedactionStmt, insertRedactionSQL},
|
||||
{&s.selectRedactedEventStmt, selectRedactedEventSQL},
|
||||
{&s.selectRedactionEventStmt, selectRedactionEventSQL},
|
||||
{&s.selectRedactionInfoByRedactionEventIDStmt, selectRedactionInfoByRedactionEventIDSQL},
|
||||
{&s.selectRedactionInfoByEventBeingRedactedStmt, selectRedactionInfoByEventBeingRedactedSQL},
|
||||
{&s.markRedactionValidatedStmt, markRedactionValidatedSQL},
|
||||
}.Prepare(db)
|
||||
}
|
||||
|
@ -82,32 +82,32 @@ func (s *redactionStatements) InsertRedaction(
|
|||
return err
|
||||
}
|
||||
|
||||
func (s *redactionStatements) SelectRedactedEvent(
|
||||
func (s *redactionStatements) SelectRedactionInfoByRedactionEventID(
|
||||
ctx context.Context, txn *sql.Tx, redactionEventID string,
|
||||
) (info *tables.RedactionInfo, err error) {
|
||||
info = &tables.RedactionInfo{}
|
||||
stmt := sqlutil.TxStmt(txn, s.selectRedactedEventStmt)
|
||||
stmt := sqlutil.TxStmt(txn, s.selectRedactionInfoByRedactionEventIDStmt)
|
||||
err = stmt.QueryRowContext(ctx, redactionEventID).Scan(
|
||||
&info.RedactionEventID, &info.RedactsEventID, &info.Validated,
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
info = nil
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *redactionStatements) SelectRedactionEvent(
|
||||
ctx context.Context, txn *sql.Tx, redactedEventID string,
|
||||
func (s *redactionStatements) SelectRedactionInfoByEventBeingRedacted(
|
||||
ctx context.Context, txn *sql.Tx, eventID string,
|
||||
) (info *tables.RedactionInfo, err error) {
|
||||
info = &tables.RedactionInfo{}
|
||||
stmt := sqlutil.TxStmt(txn, s.selectRedactionEventStmt)
|
||||
err = stmt.QueryRowContext(ctx, redactedEventID).Scan(
|
||||
stmt := sqlutil.TxStmt(txn, s.selectRedactionInfoByEventBeingRedactedStmt)
|
||||
err = stmt.QueryRowContext(ctx, eventID).Scan(
|
||||
&info.RedactionEventID, &info.RedactsEventID, &info.Validated,
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
info = nil
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
|
@ -345,13 +344,15 @@ func (d *Database) GetLatestEventsForUpdate(
|
|||
func (d *Database) StoreEvent(
|
||||
ctx context.Context, event gomatrixserverlib.Event,
|
||||
txnAndSessionID *api.TransactionID, authEventNIDs []types.EventNID,
|
||||
) (types.RoomNID, types.StateAtEvent, error) {
|
||||
) (types.RoomNID, types.StateAtEvent, *gomatrixserverlib.Event, string, error) {
|
||||
var (
|
||||
roomNID types.RoomNID
|
||||
eventTypeNID types.EventTypeNID
|
||||
eventStateKeyNID types.EventStateKeyNID
|
||||
eventNID types.EventNID
|
||||
stateNID types.StateSnapshotNID
|
||||
redactionEvent *gomatrixserverlib.Event
|
||||
redactedEventID string
|
||||
err error
|
||||
)
|
||||
|
||||
|
@ -419,11 +420,11 @@ func (d *Database) StoreEvent(
|
|||
if err = d.EventJSONTable.InsertEventJSON(ctx, txn, eventNID, event.JSON()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.handleRedactions(ctx, txn, eventNID, event)
|
||||
redactionEvent, redactedEventID, err = d.handleRedactions(ctx, txn, eventNID, event)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return 0, types.StateAtEvent{}, err
|
||||
return 0, types.StateAtEvent{}, nil, "", err
|
||||
}
|
||||
|
||||
return roomNID, types.StateAtEvent{
|
||||
|
@ -435,7 +436,7 @@ func (d *Database) StoreEvent(
|
|||
},
|
||||
EventNID: eventNID,
|
||||
},
|
||||
}, nil
|
||||
}, redactionEvent, redactedEventID, nil
|
||||
}
|
||||
|
||||
func (d *Database) PublishRoom(ctx context.Context, roomID string, publish bool) error {
|
||||
|
@ -531,20 +532,42 @@ func extractRoomVersionFromCreateEvent(event gomatrixserverlib.Event) (
|
|||
// When an event is redacted, the redacted event JSON is modified to add an `unsigned.redacted_because` field. We use this field
|
||||
// when loading events to determine whether to apply redactions. This keeps the hot-path of reading events quick as we don't need
|
||||
// to cross-reference with other tables when loading.
|
||||
func (d *Database) handleRedactions(ctx context.Context, txn *sql.Tx, eventNID types.EventNID, event gomatrixserverlib.Event) error {
|
||||
//
|
||||
// Returns the redaction event and the event ID of the redacted event if this call resulted in a redaction.
|
||||
func (d *Database) handleRedactions(
|
||||
ctx context.Context, txn *sql.Tx, eventNID types.EventNID, event gomatrixserverlib.Event,
|
||||
) (*gomatrixserverlib.Event, string, error) {
|
||||
var err error
|
||||
isRedactionEvent := event.Type() == gomatrixserverlib.MRoomRedaction && event.StateKey() == nil
|
||||
if isRedactionEvent {
|
||||
// an event which redacts itself should be ignored
|
||||
if event.EventID() == event.Redacts() {
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
err = d.RedactionsTable.InsertRedaction(ctx, txn, tables.RedactionInfo{
|
||||
Validated: false,
|
||||
RedactionEventID: event.EventID(),
|
||||
RedactsEventID: event.Redacts(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
|
||||
redactionEvent, redactedEvent, validated, err := d.loadRedactionPair(ctx, txn, eventNID, event)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, "", err
|
||||
}
|
||||
if validated || redactedEvent == nil || redactionEvent == nil {
|
||||
// we've seen this redaction before or there is nothing to redact
|
||||
return nil
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
// mark the event as redacted
|
||||
err = redactedEvent.SetUnsignedField("redacted_because", redactionEvent)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, "", err
|
||||
}
|
||||
if redactionsArePermanent {
|
||||
redactedEvent.Event = redactedEvent.Redact()
|
||||
|
@ -552,82 +575,51 @@ func (d *Database) handleRedactions(ctx context.Context, txn *sql.Tx, eventNID t
|
|||
// overwrite the eventJSON table
|
||||
err = d.EventJSONTable.InsertEventJSON(ctx, txn, redactedEvent.EventNID, redactedEvent.JSON())
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return d.RedactionsTable.MarkRedactionValidated(ctx, txn, redactionEvent.EventID(), true)
|
||||
return &redactionEvent.Event, redactedEvent.EventID(), d.RedactionsTable.MarkRedactionValidated(ctx, txn, redactionEvent.EventID(), true)
|
||||
}
|
||||
|
||||
// loadRedactionPair returns both the redaction event and the redacted event, else nil.
|
||||
// nolint:gocyclo
|
||||
func (d *Database) loadRedactionPair(
|
||||
ctx context.Context, txn *sql.Tx, eventNID types.EventNID, event gomatrixserverlib.Event,
|
||||
) (*types.Event, *types.Event, bool, error) {
|
||||
var redactionEvent, redactedEvent *types.Event
|
||||
var info *tables.RedactionInfo
|
||||
var nids map[string]types.EventNID
|
||||
var evs []types.Event
|
||||
var err error
|
||||
isRedactionEvent := event.Type() == gomatrixserverlib.MRoomRedaction && event.StateKey() == nil
|
||||
|
||||
var eventBeingRedacted string
|
||||
if isRedactionEvent {
|
||||
eventBeingRedacted = event.Redacts()
|
||||
redactionEvent = &types.Event{
|
||||
EventNID: eventNID,
|
||||
Event: event,
|
||||
}
|
||||
// find the redacted event if one exists
|
||||
info, err = d.RedactionsTable.SelectRedactedEvent(ctx, txn, event.EventID())
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
if info == nil {
|
||||
// we don't have the redacted event yet
|
||||
return nil, nil, false, nil
|
||||
}
|
||||
nids, err = d.EventNIDs(ctx, []string{info.RedactsEventID})
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
if len(nids) == 0 {
|
||||
return nil, nil, false, fmt.Errorf("redaction: missing event NID being redacted: %+v", info)
|
||||
}
|
||||
evs, err = d.Events(ctx, []types.EventNID{nids[info.RedactsEventID]})
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
if len(evs) != 1 {
|
||||
return nil, nil, false, fmt.Errorf("redaction: missing event being redacted: %+v", info)
|
||||
}
|
||||
redactedEvent = &evs[0]
|
||||
} else {
|
||||
eventBeingRedacted = event.EventID() // maybe, we'll see if we have info
|
||||
redactedEvent = &types.Event{
|
||||
EventNID: eventNID,
|
||||
Event: event,
|
||||
}
|
||||
// find the redaction event if one exists
|
||||
info, err = d.RedactionsTable.SelectRedactionEvent(ctx, txn, event.EventID())
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
if info == nil {
|
||||
// this event is not redacted
|
||||
return nil, nil, false, nil
|
||||
}
|
||||
nids, err = d.EventNIDs(ctx, []string{info.RedactionEventID})
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
if len(nids) == 0 {
|
||||
return nil, nil, false, fmt.Errorf("redaction: missing redaction event NID: %+v", info)
|
||||
}
|
||||
evs, err = d.Events(ctx, []types.EventNID{nids[info.RedactionEventID]})
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
if len(evs) != 1 {
|
||||
return nil, nil, false, fmt.Errorf("redaction: missing redaction event: %+v", info)
|
||||
}
|
||||
redactionEvent = &evs[0]
|
||||
}
|
||||
|
||||
info, err = d.RedactionsTable.SelectRedactionInfoByEventBeingRedacted(ctx, txn, eventBeingRedacted)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
if info == nil {
|
||||
// this event hasn't been redacted or we don't have the redaction for it yet
|
||||
return nil, nil, false, nil
|
||||
}
|
||||
|
||||
if isRedactionEvent {
|
||||
redactedEvent = d.loadEvent(ctx, info.RedactsEventID)
|
||||
} else {
|
||||
redactionEvent = d.loadEvent(ctx, info.RedactionEventID)
|
||||
}
|
||||
|
||||
return redactionEvent, redactedEvent, info.Validated, nil
|
||||
}
|
||||
|
||||
|
@ -639,3 +631,22 @@ func (d *Database) applyRedactions(events []types.Event) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loadEvent loads a single event or returns nil on any problems/missing event
|
||||
func (d *Database) loadEvent(ctx context.Context, eventID string) *types.Event {
|
||||
nids, err := d.EventNIDs(ctx, []string{eventID})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if len(nids) == 0 {
|
||||
return nil
|
||||
}
|
||||
evs, err := d.Events(ctx, []types.EventNID{nids[eventID]})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if len(evs) != 1 {
|
||||
return nil
|
||||
}
|
||||
return &evs[0]
|
||||
}
|
||||
|
|
|
@ -40,11 +40,11 @@ const insertRedactionSQL = "" +
|
|||
"INSERT INTO roomserver_redactions (redaction_event_id, redacts_event_id, validated)" +
|
||||
" VALUES ($1, $2, $3)"
|
||||
|
||||
const selectRedactedEventSQL = "" +
|
||||
const selectRedactionInfoByRedactionEventIDSQL = "" +
|
||||
"SELECT redaction_event_id, redacts_event_id, validated FROM roomserver_redactions" +
|
||||
" WHERE redaction_event_id = $1"
|
||||
|
||||
const selectRedactionEventSQL = "" +
|
||||
const selectRedactionInfoByEventBeingRedactedSQL = "" +
|
||||
"SELECT redaction_event_id, redacts_event_id, validated FROM roomserver_redactions" +
|
||||
" WHERE redacts_event_id = $1"
|
||||
|
||||
|
@ -52,10 +52,10 @@ const markRedactionValidatedSQL = "" +
|
|||
" UPDATE roomserver_redactions SET validated = $2 WHERE redaction_event_id = $1"
|
||||
|
||||
type redactionStatements struct {
|
||||
insertRedactionStmt *sql.Stmt
|
||||
selectRedactedEventStmt *sql.Stmt
|
||||
selectRedactionEventStmt *sql.Stmt
|
||||
markRedactionValidatedStmt *sql.Stmt
|
||||
insertRedactionStmt *sql.Stmt
|
||||
selectRedactionInfoByRedactionEventIDStmt *sql.Stmt
|
||||
selectRedactionInfoByEventBeingRedactedStmt *sql.Stmt
|
||||
markRedactionValidatedStmt *sql.Stmt
|
||||
}
|
||||
|
||||
func NewSqliteRedactionsTable(db *sql.DB) (tables.Redactions, error) {
|
||||
|
@ -67,8 +67,8 @@ func NewSqliteRedactionsTable(db *sql.DB) (tables.Redactions, error) {
|
|||
|
||||
return s, shared.StatementList{
|
||||
{&s.insertRedactionStmt, insertRedactionSQL},
|
||||
{&s.selectRedactedEventStmt, selectRedactedEventSQL},
|
||||
{&s.selectRedactionEventStmt, selectRedactionEventSQL},
|
||||
{&s.selectRedactionInfoByRedactionEventIDStmt, selectRedactionInfoByRedactionEventIDSQL},
|
||||
{&s.selectRedactionInfoByEventBeingRedactedStmt, selectRedactionInfoByEventBeingRedactedSQL},
|
||||
{&s.markRedactionValidatedStmt, markRedactionValidatedSQL},
|
||||
}.Prepare(db)
|
||||
}
|
||||
|
@ -81,11 +81,11 @@ func (s *redactionStatements) InsertRedaction(
|
|||
return err
|
||||
}
|
||||
|
||||
func (s *redactionStatements) SelectRedactedEvent(
|
||||
func (s *redactionStatements) SelectRedactionInfoByRedactionEventID(
|
||||
ctx context.Context, txn *sql.Tx, redactionEventID string,
|
||||
) (info *tables.RedactionInfo, err error) {
|
||||
info = &tables.RedactionInfo{}
|
||||
stmt := sqlutil.TxStmt(txn, s.selectRedactedEventStmt)
|
||||
stmt := sqlutil.TxStmt(txn, s.selectRedactionInfoByRedactionEventIDStmt)
|
||||
err = stmt.QueryRowContext(ctx, redactionEventID).Scan(
|
||||
&info.RedactionEventID, &info.RedactsEventID, &info.Validated,
|
||||
)
|
||||
|
@ -96,12 +96,12 @@ func (s *redactionStatements) SelectRedactedEvent(
|
|||
return
|
||||
}
|
||||
|
||||
func (s *redactionStatements) SelectRedactionEvent(
|
||||
ctx context.Context, txn *sql.Tx, redactedEventID string,
|
||||
func (s *redactionStatements) SelectRedactionInfoByEventBeingRedacted(
|
||||
ctx context.Context, txn *sql.Tx, eventID string,
|
||||
) (info *tables.RedactionInfo, err error) {
|
||||
info = &tables.RedactionInfo{}
|
||||
stmt := sqlutil.TxStmt(txn, s.selectRedactionEventStmt)
|
||||
err = stmt.QueryRowContext(ctx, redactedEventID).Scan(
|
||||
stmt := sqlutil.TxStmt(txn, s.selectRedactionInfoByEventBeingRedactedStmt)
|
||||
err = stmt.QueryRowContext(ctx, eventID).Scan(
|
||||
&info.RedactionEventID, &info.RedactsEventID, &info.Validated,
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
|
|
|
@ -139,10 +139,10 @@ type RedactionInfo struct {
|
|||
|
||||
type Redactions interface {
|
||||
InsertRedaction(ctx context.Context, txn *sql.Tx, info RedactionInfo) error
|
||||
// SelectRedactedEvent returns the redaction info for the given redaction event ID, or nil if there is no match.
|
||||
SelectRedactedEvent(ctx context.Context, txn *sql.Tx, redactionEventID string) (*RedactionInfo, error)
|
||||
// SelectRedactionEvent returns the redaction info for the given redacted event ID, or nil if there is no match.
|
||||
SelectRedactionEvent(ctx context.Context, txn *sql.Tx, redactedEventID string) (*RedactionInfo, error)
|
||||
// SelectRedactionInfoByRedactionEventID returns the redaction info for the given redaction event ID, or nil if there is no match.
|
||||
SelectRedactionInfoByRedactionEventID(ctx context.Context, txn *sql.Tx, redactionEventID string) (*RedactionInfo, error)
|
||||
// SelectRedactionInfoByEventBeingRedacted returns the redaction info for the given redacted event ID, or nil if there is no match.
|
||||
SelectRedactionInfoByEventBeingRedacted(ctx context.Context, txn *sql.Tx, eventID string) (*RedactionInfo, error)
|
||||
// Mark this redaction event as having been validated. This means we have both sides of the redaction and have
|
||||
// successfully redacted the event JSON.
|
||||
MarkRedactionValidated(ctx context.Context, txn *sql.Tx, redactionEventID string, validated bool) error
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue