dendrite/internal/eventutil/events.go
Kegan Dougal 4d93d921be
Some checks are pending
Dendrite / Sytest (SQLite Cgo) (push) Blocked by required conditions
Dendrite / WASM build test (push) Waiting to run
Dendrite / Linting (push) Waiting to run
Dendrite / Unit tests (push) Waiting to run
Dendrite / Build for Linux (push) Waiting to run
Dendrite / Build for Windows (push) Waiting to run
Dendrite / Initial tests passed (push) Blocked by required conditions
Dendrite / Integration tests (push) Blocked by required conditions
Dendrite / Upgrade tests (push) Blocked by required conditions
Dendrite / Upgrade tests from HEAD-2 (push) Blocked by required conditions
Dendrite / Sytest (PostgreSQL) (push) Blocked by required conditions
Dendrite / Sytest (SQLite native) (push) Blocked by required conditions
Dendrite / Complement (PostgreSQL) (push) Blocked by required conditions
Dendrite / Complement (SQLite native) (push) Blocked by required conditions
Dendrite / Complement (SQLite Cgo) (push) Blocked by required conditions
Dendrite / Integration tests passed (push) Blocked by required conditions
Dendrite / Update Docker images (push) Blocked by required conditions
Room version 12 (#3623)
2025-08-11 20:59:47 +01:00

208 lines
6.7 KiB
Go

// Copyright 2024 New Vector Ltd.
// Copyright 2020 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
package eventutil
import (
"context"
"errors"
"fmt"
"time"
"github.com/element-hq/dendrite/roomserver/api"
"github.com/element-hq/dendrite/roomserver/types"
"github.com/element-hq/dendrite/syncapi/synctypes"
"github.com/matrix-org/gomatrixserverlib/fclient"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/gomatrixserverlib"
)
// ErrRoomNoExists is returned when trying to lookup the state of a room that
// doesn't exist
var errRoomNoExists = fmt.Errorf("room does not exist")
type ErrRoomNoExists struct{}
func (e ErrRoomNoExists) Error() string {
return errRoomNoExists.Error()
}
func (e ErrRoomNoExists) Unwrap() error {
return errRoomNoExists
}
// QueryAndBuildEvent builds a Matrix event using the event builder and roomserver query
// API client provided. If also fills roomserver query API response (if provided)
// in case the function calling FillBuilder needs to use it.
// Returns ErrRoomNoExists if the state of the room could not be retrieved because
// the room doesn't exist
// Returns an error if something else went wrong
func QueryAndBuildEvent(
ctx context.Context,
proto *gomatrixserverlib.ProtoEvent,
identity *fclient.SigningIdentity, evTime time.Time,
rsAPI api.QueryLatestEventsAndStateAPI, queryRes *api.QueryLatestEventsAndStateResponse,
) (*types.HeaderedEvent, error) {
if queryRes == nil {
queryRes = &api.QueryLatestEventsAndStateResponse{}
}
eventsNeeded, err := queryRequiredEventsForBuilder(ctx, proto, rsAPI, queryRes)
if err != nil {
// This can pass through a ErrRoomNoExists to the caller
return nil, err
}
return BuildEvent(ctx, proto, identity, evTime, eventsNeeded, queryRes)
}
// BuildEvent builds a Matrix event from the builder and QueryLatestEventsAndStateResponse
// provided.
func BuildEvent(
ctx context.Context,
proto *gomatrixserverlib.ProtoEvent,
identity *fclient.SigningIdentity, evTime time.Time,
eventsNeeded *gomatrixserverlib.StateNeeded, queryRes *api.QueryLatestEventsAndStateResponse,
) (*types.HeaderedEvent, error) {
verImpl, err := gomatrixserverlib.GetRoomVersion(queryRes.RoomVersion)
if err != nil {
return nil, err
}
proto.Version = verImpl
if err = addPrevEventsToEvent(proto, eventsNeeded, queryRes); err != nil {
return nil, err
}
builder := verImpl.NewEventBuilderFromProtoEvent(proto)
if verImpl.DomainlessRoomIDs() && builder.RoomID != "" && proto.Type == spec.MRoomCreate && proto.StateKey != nil && *proto.StateKey == "" {
return nil, gomatrixserverlib.EventValidationError{
Message: "cannot resend m.room.create event",
Code: 400,
}
}
event, err := builder.Build(
evTime, identity.ServerName, identity.KeyID,
identity.PrivateKey,
)
if err != nil {
return nil, err
}
return &types.HeaderedEvent{PDU: event}, nil
}
// queryRequiredEventsForBuilder queries the roomserver for auth/prev events needed for this builder.
func queryRequiredEventsForBuilder(
ctx context.Context,
proto *gomatrixserverlib.ProtoEvent,
rsAPI api.QueryLatestEventsAndStateAPI, queryRes *api.QueryLatestEventsAndStateResponse,
) (*gomatrixserverlib.StateNeeded, error) {
eventsNeeded, err := gomatrixserverlib.StateNeededForProtoEvent(proto)
if err != nil {
return nil, fmt.Errorf("gomatrixserverlib.StateNeededForProtoEvent: %w", err)
}
if len(eventsNeeded.Tuples()) == 0 {
return nil, errors.New("expecting state tuples for event builder, got none")
}
// Ask the roomserver for information about this room
queryReq := api.QueryLatestEventsAndStateRequest{
RoomID: proto.RoomID,
StateToFetch: eventsNeeded.Tuples(),
}
return &eventsNeeded, rsAPI.QueryLatestEventsAndState(ctx, &queryReq, queryRes)
}
// addPrevEventsToEvent fills out the prev_events and auth_events fields in builder
func addPrevEventsToEvent(
builder *gomatrixserverlib.ProtoEvent,
eventsNeeded *gomatrixserverlib.StateNeeded,
queryRes *api.QueryLatestEventsAndStateResponse,
) error {
if !queryRes.RoomExists {
return ErrRoomNoExists{}
}
builder.Depth = queryRes.Depth
authEvents, _ := gomatrixserverlib.NewAuthEvents(nil)
for i := range queryRes.StateEvents {
err := authEvents.AddEvent(queryRes.StateEvents[i].PDU)
if err != nil {
return fmt.Errorf("authEvents.AddEvent: %w", err)
}
}
refs, err := eventsNeeded.AuthEventReferences(authEvents)
if err != nil {
return fmt.Errorf("eventsNeeded.AuthEventReferences: %w", err)
}
var authEventIDs []string
if builder.Version.DomainlessRoomIDs() && len(builder.RoomID) > 0 {
// the room ID is the create event so we shouldn't set it in auth_events
authEventIDs = make([]string, 0, len(refs))
createEventID := fmt.Sprintf("$%s", builder.RoomID[1:])
for _, id := range refs {
if id == createEventID {
continue
}
authEventIDs = append(authEventIDs, id)
}
} else {
authEventIDs = refs
}
builder.AuthEvents, builder.PrevEvents = truncateAuthAndPrevEvents(authEventIDs, queryRes.LatestEvents)
return nil
}
// truncateAuthAndPrevEvents limits the number of events we add into
// an event as prev_events or auth_events.
// NOTSPEC: The limits here feel a bit arbitrary but they are currently
// here because of https://github.com/matrix-org/matrix-doc/issues/2307
// and because Synapse will just drop events that don't comply.
func truncateAuthAndPrevEvents(auth, prev []string) (
truncAuth, truncPrev []string,
) {
truncAuth, truncPrev = auth, prev
if len(truncAuth) > 10 {
truncAuth = truncAuth[:10]
}
if len(truncPrev) > 20 {
truncPrev = truncPrev[:20]
}
return
}
// RedactEvent redacts the given event and sets the unsigned field appropriately. This should be used by
// downstream components to the roomserver when an OutputTypeRedactedEvent occurs.
func RedactEvent(ctx context.Context, redactionEvent, redactedEvent gomatrixserverlib.PDU, querier api.QuerySenderIDAPI) error {
// sanity check
if redactionEvent.Type() != spec.MRoomRedaction {
return fmt.Errorf("RedactEvent: redactionEvent isn't a redaction event, is '%s'", redactionEvent.Type())
}
redactedEvent.Redact()
clientEvent, err := synctypes.ToClientEvent(redactionEvent, synctypes.FormatSync, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
return querier.QueryUserIDForSender(ctx, roomID, senderID)
})
if err != nil {
return err
}
redactedBecause := clientEvent
if err := redactedEvent.SetUnsignedField("redacted_because", redactedBecause); err != nil {
return err
}
// NOTSPEC: sytest relies on this unspecced field existing :(
if err := redactedEvent.SetUnsignedField("redacted_by", redactionEvent.EventID()); err != nil {
return err
}
return nil
}