mirror of
https://github.com/element-hq/dendrite.git
synced 2025-09-13 12:52:24 +03:00
Room version 12 (#3623)
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
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
This commit is contained in:
parent
a408b24d28
commit
4d93d921be
26 changed files with 530 additions and 208 deletions
|
@ -11,6 +11,7 @@ import (
|
|||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
asAPI "github.com/element-hq/dendrite/appservice/api"
|
||||
|
@ -134,7 +135,7 @@ func (r *RoomserverInternalAPI) RemoveRoomAlias(ctx context.Context, senderID sp
|
|||
}
|
||||
|
||||
if spec.SenderID(creatorID) != senderID {
|
||||
var plEvent *types.HeaderedEvent
|
||||
var createEvent, plEvent *types.HeaderedEvent
|
||||
var pls *gomatrixserverlib.PowerLevelContent
|
||||
|
||||
plEvent, err = r.DB.GetStateEvent(ctx, roomID, spec.MRoomPowerLevels, "")
|
||||
|
@ -147,7 +148,14 @@ func (r *RoomserverInternalAPI) RemoveRoomAlias(ctx context.Context, senderID sp
|
|||
return true, false, fmt.Errorf("plEvent.PowerLevels: %w", err)
|
||||
}
|
||||
|
||||
if pls.UserLevel(senderID) < pls.EventLevel(spec.MRoomCanonicalAlias, true) {
|
||||
createEvent, err = r.DB.GetStateEvent(ctx, roomID, spec.MRoomCreate, "")
|
||||
if err != nil {
|
||||
return true, false, fmt.Errorf("r.DB.GetStateEvent: %w", err)
|
||||
}
|
||||
isPrivilegedCreator := gomatrixserverlib.MustGetRoomVersion(createEvent.Version()).PrivilegedCreators() &&
|
||||
slices.Contains(gomatrixserverlib.CreatorsFromCreateEvent(createEvent), string(senderID))
|
||||
|
||||
if !isPrivilegedCreator && pls.UserLevel(senderID) < pls.EventLevel(spec.MRoomCanonicalAlias, true) {
|
||||
return true, false, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -330,6 +330,7 @@ func (w *worker) _next() {
|
|||
// a string, because we might want to return that to the caller if
|
||||
// it was a synchronous request.
|
||||
var errString string
|
||||
wasRejected := false
|
||||
if err = w.r.processRoomEvent(
|
||||
w.r.ProcessContext.Context(),
|
||||
spec.ServerName(msg.Header.Get("virtual_host")),
|
||||
|
@ -343,6 +344,7 @@ func (w *worker) _next() {
|
|||
"event_id": inputRoomEvent.Event.EventID(),
|
||||
"type": inputRoomEvent.Event.Type(),
|
||||
}).Warn("Roomserver rejected event")
|
||||
wasRejected = true
|
||||
default:
|
||||
if !errors.Is(err, context.DeadlineExceeded) && !errors.Is(err, context.Canceled) {
|
||||
w.sentryHub.CaptureException(err)
|
||||
|
@ -364,6 +366,10 @@ func (w *worker) _next() {
|
|||
_ = msg.AckSync()
|
||||
}
|
||||
|
||||
if wasRejected {
|
||||
errString = api.InputWasRejected
|
||||
}
|
||||
|
||||
// If it was a synchronous input request then the "sync" field
|
||||
// will be present in the message. That means that someone is
|
||||
// waiting for a response. The temporary inbox name is present in
|
||||
|
|
|
@ -39,6 +39,7 @@ type Creator struct {
|
|||
// PerformCreateRoom handles all the steps necessary to create a new room.
|
||||
// nolint: gocyclo
|
||||
func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roomID spec.RoomID, createRequest *api.PerformCreateRoomRequest) (string, *util.JSONResponse) {
|
||||
// Make sure we know the room version
|
||||
verImpl, err := gomatrixserverlib.GetRoomVersion(createRequest.RoomVersion)
|
||||
if err != nil {
|
||||
return "", &util.JSONResponse{
|
||||
|
@ -47,17 +48,7 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo
|
|||
}
|
||||
}
|
||||
|
||||
createContent := map[string]interface{}{}
|
||||
if len(createRequest.CreationContent) > 0 {
|
||||
if err = json.Unmarshal(createRequest.CreationContent, &createContent); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for creation_content failed")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.BadJSON("invalid create content"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate the room
|
||||
_, err = c.DB.AssignRoomNID(ctx, roomID, createRequest.RoomVersion)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("failed to assign roomNID")
|
||||
|
@ -67,6 +58,7 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo
|
|||
}
|
||||
}
|
||||
|
||||
// Allocate the user
|
||||
var senderID spec.SenderID
|
||||
if createRequest.RoomVersion == gomatrixserverlib.RoomVersionPseudoIDs {
|
||||
// create user room key if needed
|
||||
|
@ -83,17 +75,73 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo
|
|||
senderID = spec.SenderID(userID.String())
|
||||
}
|
||||
|
||||
// TODO: Maybe, at some point, GMSL should return the events to create, so we can define the version
|
||||
// entirely there.
|
||||
switch createRequest.RoomVersion {
|
||||
case gomatrixserverlib.RoomVersionV11:
|
||||
// RoomVersionV11 removed the creator field from the create content: https://github.com/matrix-org/matrix-spec-proposals/pull/2175
|
||||
default:
|
||||
createContent["creator"] = senderID
|
||||
// get the signing identity
|
||||
identity, err := c.Cfg.Matrix.SigningIdentityFor(userID.Domain()) // we MUST use the server signing mxid_mapping
|
||||
if err != nil {
|
||||
logrus.WithError(err).WithField("domain", userID.Domain()).Error("unable to find signing identity for domain")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
createContent["room_version"] = createRequest.RoomVersion
|
||||
powerLevelContent := eventutil.InitialPowerLevelsContent(string(senderID))
|
||||
// Make the create event if we need to
|
||||
var (
|
||||
createEvent gomatrixserverlib.PDU
|
||||
jsonErr *util.JSONResponse
|
||||
)
|
||||
authEvents, _ := gomatrixserverlib.NewAuthEvents(nil)
|
||||
if createRequest.CreateEvent != nil {
|
||||
createEvent, err = verImpl.NewEventFromTrustedJSON(createRequest.CreateEvent, false)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.NewEventFromTrustedJSON failed to verify create event")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
if err = authEvents.AddEvent(createEvent); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.AuthEvents.AddEvent failed")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var additionalCreators []string
|
||||
if createRequest.StatePreset == spec.PresetTrustedPrivateChat {
|
||||
additionalCreators = createRequest.InvitedUsers
|
||||
}
|
||||
createContent, contentErr := api.GenerateCreateContent(ctx, createRequest.RoomVersion, string(senderID), createRequest.CreationContent, additionalCreators)
|
||||
if contentErr != nil {
|
||||
util.GetLogger(ctx).WithError(contentErr).Error("GenerateCreateContent failed")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.BadJSON("invalid create content"),
|
||||
}
|
||||
}
|
||||
createEvent, jsonErr = api.GeneratePDU(
|
||||
ctx, verImpl,
|
||||
gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomCreate,
|
||||
Content: createContent,
|
||||
},
|
||||
authEvents, 1, "", identity, createRequest.EventTime, string(senderID), roomID.String(), c.RSAPI,
|
||||
)
|
||||
if jsonErr != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("Failed to make the create event")
|
||||
return "", jsonErr
|
||||
}
|
||||
if err = authEvents.AddEvent(createEvent); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("authEvents.AddEvent failed")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
powerLevelContent := eventutil.InitialPowerLevelsContent(verImpl, string(senderID))
|
||||
joinRuleContent := gomatrixserverlib.JoinRuleContent{
|
||||
JoinRule: spec.Invite,
|
||||
}
|
||||
|
@ -122,8 +170,10 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo
|
|||
case spec.PresetTrustedPrivateChat:
|
||||
joinRuleContent.JoinRule = spec.Invite
|
||||
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
|
||||
for _, invitee := range createRequest.InvitedUsers {
|
||||
powerLevelContent.Users[invitee] = 100
|
||||
if !verImpl.PrivilegedCreators() {
|
||||
for _, invitee := range createRequest.InvitedUsers {
|
||||
powerLevelContent.Users[invitee] = 100
|
||||
}
|
||||
}
|
||||
guestsCanJoin = true
|
||||
case spec.PresetPublicChat:
|
||||
|
@ -131,10 +181,6 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo
|
|||
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
|
||||
}
|
||||
|
||||
createEvent := gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomCreate,
|
||||
Content: createContent,
|
||||
}
|
||||
powerLevelEvent := gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomPowerLevels,
|
||||
Content: powerLevelContent,
|
||||
|
@ -158,16 +204,6 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo
|
|||
AvatarURL: createRequest.UserAvatarURL,
|
||||
}
|
||||
|
||||
// get the signing identity
|
||||
identity, err := c.Cfg.Matrix.SigningIdentityFor(userID.Domain()) // we MUST use the server signing mxid_mapping
|
||||
if err != nil {
|
||||
logrus.WithError(err).WithField("domain", userID.Domain()).Error("unable to find signing identity for domain")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
// If we are creating a room with pseudo IDs, create and sign the MXIDMapping
|
||||
if createRequest.RoomVersion == gomatrixserverlib.RoomVersionPseudoIDs {
|
||||
var pseudoIDKey ed25519.PrivateKey
|
||||
|
@ -279,7 +315,6 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo
|
|||
switch createRequest.InitialState[i].Type {
|
||||
case spec.MRoomCreate:
|
||||
continue
|
||||
|
||||
case spec.MRoomPowerLevels:
|
||||
powerLevelEvent = createRequest.InitialState[i]
|
||||
|
||||
|
@ -321,7 +356,8 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo
|
|||
// harder to reason about, hence sticking to a strict static ordering.
|
||||
// TODO: Synapse has txn/token ID on each event. Do we need to do this here?
|
||||
eventsToMake := []gomatrixserverlib.FledglingEvent{
|
||||
createEvent, membershipEvent, powerLevelEvent, joinRuleEvent, historyVisibilityEvent,
|
||||
// we made the create event already hence it isn't here.
|
||||
membershipEvent, powerLevelEvent, joinRuleEvent, historyVisibilityEvent,
|
||||
}
|
||||
if guestAccessEvent != nil {
|
||||
eventsToMake = append(eventsToMake, *guestAccessEvent)
|
||||
|
@ -342,61 +378,19 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo
|
|||
// TODO: invite events
|
||||
// TODO: 3pid invite events
|
||||
|
||||
var builtEvents []*types.HeaderedEvent
|
||||
authEvents, _ := gomatrixserverlib.NewAuthEvents(nil)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("rsapi.QuerySenderIDForUser failed")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
builtEvents := []*types.HeaderedEvent{
|
||||
{PDU: createEvent},
|
||||
}
|
||||
for i, e := range eventsToMake {
|
||||
depth := i + 1 // depth starts at 1
|
||||
depth := i + 2 // depth starts at 2 since we made the create event already
|
||||
|
||||
builder := verImpl.NewEventBuilderFromProtoEvent(&gomatrixserverlib.ProtoEvent{
|
||||
SenderID: string(senderID),
|
||||
RoomID: roomID.String(),
|
||||
Type: e.Type,
|
||||
StateKey: &e.StateKey,
|
||||
Depth: int64(depth),
|
||||
})
|
||||
err = builder.SetContent(e.Content)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("builder.SetContent failed")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
if i > 0 {
|
||||
builder.PrevEvents = []string{builtEvents[i-1].EventID()}
|
||||
}
|
||||
var ev gomatrixserverlib.PDU
|
||||
if err = builder.AddAuthEvents(authEvents); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("AddAuthEvents failed")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
ev, err = builder.Build(createRequest.EventTime, identity.ServerName, identity.KeyID, identity.PrivateKey)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("buildEvent failed")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
if err = gomatrixserverlib.Allowed(ev, authEvents, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
||||
return c.RSAPI.QueryUserIDForSender(ctx, roomID, senderID)
|
||||
}); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.Allowed failed")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
ev, jsonErr := api.GeneratePDU(
|
||||
ctx, verImpl, e,
|
||||
authEvents, depth, builtEvents[len(builtEvents)-1].EventID(),
|
||||
identity, createRequest.EventTime, string(senderID), roomID.String(), c.RSAPI,
|
||||
)
|
||||
if jsonErr != nil {
|
||||
return "", jsonErr
|
||||
}
|
||||
|
||||
// Add the event to the list of auth events
|
||||
|
|
|
@ -156,19 +156,11 @@ func (r *Joiner) performJoinRoomByID(
|
|||
}
|
||||
}
|
||||
|
||||
// Get the domain part of the room ID.
|
||||
roomID, err := spec.NewRoomID(req.RoomIDOrAlias)
|
||||
if err != nil {
|
||||
return "", "", rsAPI.ErrInvalidID{Err: fmt.Errorf("room ID %q is invalid: %w", req.RoomIDOrAlias, err)}
|
||||
}
|
||||
|
||||
// If the server name in the room ID isn't ours then it's a
|
||||
// possible candidate for finding the room via federation. Add
|
||||
// it to the list of servers to try.
|
||||
if !r.Cfg.Matrix.IsLocalServerName(roomID.Domain()) {
|
||||
req.ServerNames = append(req.ServerNames, roomID.Domain())
|
||||
}
|
||||
|
||||
// Force a federated join if we aren't in the room and we've been
|
||||
// given some server names to try joining by.
|
||||
inRoomReq := &rsAPI.QueryServerJoinedToRoomRequest{
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/element-hq/dendrite/internal/eventutil"
|
||||
|
@ -30,14 +31,15 @@ type Upgrader struct {
|
|||
// PerformRoomUpgrade upgrades a room from one version to another
|
||||
func (r *Upgrader) PerformRoomUpgrade(
|
||||
ctx context.Context,
|
||||
roomID string, userID spec.UserID, roomVersion gomatrixserverlib.RoomVersion,
|
||||
roomID string, userID spec.UserID, roomVersion gomatrixserverlib.RoomVersion, additionalCreators []string,
|
||||
) (newRoomID string, err error) {
|
||||
return r.performRoomUpgrade(ctx, roomID, userID, roomVersion)
|
||||
return r.performRoomUpgrade(ctx, roomID, userID, roomVersion, additionalCreators)
|
||||
}
|
||||
|
||||
// nolint:gocyclo
|
||||
func (r *Upgrader) performRoomUpgrade(
|
||||
ctx context.Context,
|
||||
roomID string, userID spec.UserID, roomVersion gomatrixserverlib.RoomVersion,
|
||||
roomID string, userID spec.UserID, roomVersion gomatrixserverlib.RoomVersion, additionalCreators []string,
|
||||
) (string, error) {
|
||||
evTime := time.Now()
|
||||
|
||||
|
@ -64,35 +66,110 @@ func (r *Upgrader) performRoomUpgrade(
|
|||
return "", api.ErrNotAllowed{Err: fmt.Errorf("You don't have permission to upgrade the room, power level too low.")}
|
||||
}
|
||||
|
||||
// TODO (#267): Check room ID doesn't clash with an existing one, and we
|
||||
// probably shouldn't be using pseudo-random strings, maybe GUIDs?
|
||||
newRoomID := fmt.Sprintf("!%s:%s", util.RandomString(16), userID.Domain())
|
||||
|
||||
// Get the existing room state for the old room.
|
||||
oldRoomReq := &api.QueryLatestEventsAndStateRequest{
|
||||
RoomID: roomID,
|
||||
}
|
||||
oldRoomRes := &api.QueryLatestEventsAndStateResponse{}
|
||||
if err := r.URSAPI.QueryLatestEventsAndState(ctx, oldRoomReq, oldRoomRes); err != nil {
|
||||
if err = r.URSAPI.QueryLatestEventsAndState(ctx, oldRoomReq, oldRoomRes); err != nil {
|
||||
return "", fmt.Errorf("Failed to get latest state: %s", err)
|
||||
}
|
||||
|
||||
// Make the tombstone event
|
||||
tombstoneEvent, pErr := r.makeTombstoneEvent(ctx, evTime, *senderID, userID.Domain(), roomID, newRoomID)
|
||||
if pErr != nil {
|
||||
return "", pErr
|
||||
var oldCreateEvent *types.HeaderedEvent
|
||||
for _, ev := range oldRoomRes.StateEvents {
|
||||
if ev.Type() == spec.MRoomCreate && ev.StateKeyEquals("") {
|
||||
oldCreateEvent = ev
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Make the create event and calculate the new room ID.
|
||||
var newRoomID string
|
||||
newRoomVerImpl := gomatrixserverlib.MustGetRoomVersion(roomVersion)
|
||||
var tombstoneEvent *types.HeaderedEvent
|
||||
var newCreateEvent gomatrixserverlib.PDU
|
||||
var pErr error
|
||||
if !newRoomVerImpl.DomainlessRoomIDs() {
|
||||
// TODO (#267): Check room ID doesn't clash with an existing one, and we
|
||||
// probably shouldn't be using pseudo-random strings, maybe GUIDs?
|
||||
newRoomID = fmt.Sprintf("!%s:%s", util.RandomString(16), userID.Domain())
|
||||
|
||||
// Make the tombstone event
|
||||
tombstoneEvent, pErr = r.makeTombstoneEvent(ctx, evTime, *senderID, userID.Domain(), roomID, newRoomID)
|
||||
if pErr != nil {
|
||||
return "", pErr
|
||||
}
|
||||
}
|
||||
content := struct {
|
||||
Federate *bool `json:"m.federate,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Predecessor struct {
|
||||
RoomID string `json:"room_id"`
|
||||
EventID string `json:"event_id,omitempty"`
|
||||
} `json:"predecessor"`
|
||||
}{}
|
||||
// keep existing values in old room e.g type/m.federate
|
||||
if err = json.Unmarshal(oldCreateEvent.Content(), &content); err != nil {
|
||||
return "", fmt.Errorf("failed to copy old create event content to new create event: %s", err)
|
||||
}
|
||||
content.Predecessor.RoomID = roomID
|
||||
content.Predecessor.EventID = ""
|
||||
if tombstoneEvent != nil {
|
||||
content.Predecessor.EventID = tombstoneEvent.EventID()
|
||||
}
|
||||
contentJSON, err := json.Marshal(content)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to make content for new create event: %s", err)
|
||||
}
|
||||
// make the create event up-front so the roomserver can calculate the room NID to store.
|
||||
createContent, err := api.GenerateCreateContent(ctx, roomVersion, userID.String(), contentJSON, additionalCreators)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("GenerateCreateContent failed")
|
||||
return "", fmt.Errorf("failed to GenerateCreateContent")
|
||||
}
|
||||
authEvents, _ := gomatrixserverlib.NewAuthEvents(nil)
|
||||
identity, err := r.Cfg.Matrix.SigningIdentityFor(userID.Domain())
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("Failed to get signing identity")
|
||||
return "", fmt.Errorf("No SigningIdentityFor domain %s", userID.Domain())
|
||||
}
|
||||
createEvent, jsonErr := api.GeneratePDU(
|
||||
ctx, gomatrixserverlib.MustGetRoomVersion(roomVersion),
|
||||
gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomCreate,
|
||||
Content: createContent,
|
||||
},
|
||||
// newRoomID will be empty for domainless rooms
|
||||
authEvents, 1, "", identity, evTime, userID.String(), newRoomID, r.URSAPI,
|
||||
)
|
||||
if jsonErr != nil {
|
||||
util.GetLogger(ctx).Error("Failed to make the create event")
|
||||
return "", fmt.Errorf("failed to create new create event PDU")
|
||||
}
|
||||
newCreateEvent = createEvent
|
||||
if newRoomVerImpl.DomainlessRoomIDs() {
|
||||
newRoomID = newCreateEvent.RoomID().String()
|
||||
}
|
||||
|
||||
if tombstoneEvent == nil {
|
||||
// Make the tombstone event
|
||||
tombstoneEvent, pErr = r.makeTombstoneEvent(ctx, evTime, *senderID, userID.Domain(), roomID, newRoomID)
|
||||
if pErr != nil {
|
||||
return "", pErr
|
||||
}
|
||||
}
|
||||
|
||||
creators := gomatrixserverlib.CreatorsFromCreateEvent(newCreateEvent)
|
||||
|
||||
// Generate the initial events we need to send into the new room. This includes copied state events and bans
|
||||
// as well as the power level events needed to set up the room
|
||||
eventsToMake, pErr := r.generateInitialEvents(ctx, oldRoomRes, *senderID, roomID, roomVersion, tombstoneEvent)
|
||||
eventsToMake, pErr := r.generateInitialEvents(ctx, oldRoomRes, *senderID, roomID, roomVersion, creators)
|
||||
if pErr != nil {
|
||||
return "", pErr
|
||||
}
|
||||
|
||||
// Send the setup events to the new room
|
||||
if pErr = r.sendInitialEvents(ctx, evTime, *senderID, userID.Domain(), newRoomID, roomVersion, eventsToMake); pErr != nil {
|
||||
return "", pErr
|
||||
if pErr = r.sendInitialEvents(ctx, evTime, *senderID, userID.Domain(), newRoomID, roomVersion, newCreateEvent, eventsToMake); pErr != nil {
|
||||
return "", fmt.Errorf("sendInitialEvents: %s", pErr)
|
||||
}
|
||||
|
||||
// 5. Send the tombstone event to the old room
|
||||
|
@ -296,13 +373,25 @@ func (r *Upgrader) userIsAuthorized(ctx context.Context, senderID spec.SenderID,
|
|||
if err != nil {
|
||||
return false
|
||||
}
|
||||
createEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: spec.MRoomCreate,
|
||||
StateKey: "",
|
||||
})
|
||||
if gomatrixserverlib.MustGetRoomVersion(createEvent.Version()).PrivilegedCreators() &&
|
||||
slices.Contains(gomatrixserverlib.CreatorsFromCreateEvent(createEvent), string(senderID)) {
|
||||
return true
|
||||
}
|
||||
// Check for power level required to send tombstone event (marks the current room as obsolete),
|
||||
// if not found, use the StateDefault power level
|
||||
return pl.UserLevel(senderID) >= pl.EventLevel("m.room.tombstone", true)
|
||||
}
|
||||
|
||||
// Return the events to create AFTER the new create event
|
||||
// nolint:gocyclo
|
||||
func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.QueryLatestEventsAndStateResponse, senderID spec.SenderID, roomID string, newVersion gomatrixserverlib.RoomVersion, tombstoneEvent *types.HeaderedEvent) ([]gomatrixserverlib.FledglingEvent, error) {
|
||||
func (r *Upgrader) generateInitialEvents(
|
||||
ctx context.Context, oldRoom *api.QueryLatestEventsAndStateResponse, senderID spec.SenderID, _ string, newVersion gomatrixserverlib.RoomVersion,
|
||||
creators []string) ([]gomatrixserverlib.FledglingEvent, error) {
|
||||
|
||||
state := make(map[gomatrixserverlib.StateKeyTuple]*types.HeaderedEvent, len(oldRoom.StateEvents))
|
||||
for _, event := range oldRoom.StateEvents {
|
||||
if event.StateKey() == nil {
|
||||
|
@ -350,37 +439,10 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query
|
|||
}
|
||||
}
|
||||
|
||||
oldCreateEvent := state[gomatrixserverlib.StateKeyTuple{EventType: spec.MRoomCreate, StateKey: ""}]
|
||||
oldMembershipEvent := state[gomatrixserverlib.StateKeyTuple{EventType: spec.MRoomMember, StateKey: string(senderID)}]
|
||||
oldPowerLevelsEvent := state[gomatrixserverlib.StateKeyTuple{EventType: spec.MRoomPowerLevels, StateKey: ""}]
|
||||
oldJoinRulesEvent := state[gomatrixserverlib.StateKeyTuple{EventType: spec.MRoomJoinRules, StateKey: ""}]
|
||||
|
||||
// Create the new room create event. Using a map here instead of CreateContent
|
||||
// means that we preserve any other interesting fields that might be present
|
||||
// in the create event (such as for the room types MSC).
|
||||
newCreateContent := map[string]interface{}{}
|
||||
_ = json.Unmarshal(oldCreateEvent.Content(), &newCreateContent)
|
||||
|
||||
switch newVersion {
|
||||
case gomatrixserverlib.RoomVersionV11:
|
||||
// RoomVersionV11 removed the creator field from the create content: https://github.com/matrix-org/matrix-spec-proposals/pull/2175
|
||||
// So if we are upgrading from pre v11, we need to remove the field.
|
||||
delete(newCreateContent, "creator")
|
||||
default:
|
||||
newCreateContent["creator"] = senderID
|
||||
}
|
||||
|
||||
newCreateContent["room_version"] = newVersion
|
||||
newCreateContent["predecessor"] = gomatrixserverlib.PreviousRoom{
|
||||
EventID: tombstoneEvent.EventID(),
|
||||
RoomID: roomID,
|
||||
}
|
||||
newCreateEvent := gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomCreate,
|
||||
StateKey: "",
|
||||
Content: newCreateContent,
|
||||
}
|
||||
|
||||
// Now create the new membership event. Same rules apply as above, so
|
||||
// that we preserve fields we don't otherwise know about. We'll always
|
||||
// set the membership to join though, because that is necessary to auth
|
||||
|
@ -405,7 +467,9 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query
|
|||
return nil, fmt.Errorf("Power level event content was invalid")
|
||||
}
|
||||
|
||||
tempPowerLevelsEvent, powerLevelsOverridden := createTemporaryPowerLevels(powerLevelContent, senderID)
|
||||
verImpl := gomatrixserverlib.MustGetRoomVersion(newVersion)
|
||||
|
||||
tempPowerLevelsEvent, powerLevelsOverridden := createTemporaryPowerLevels(verImpl, powerLevelContent, senderID, creators)
|
||||
|
||||
// Now do the join rules event, same as the create and membership
|
||||
// events. We'll set a sane default of "invite" so that if the
|
||||
|
@ -423,7 +487,7 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query
|
|||
|
||||
eventsToMake := make([]gomatrixserverlib.FledglingEvent, 0, len(state))
|
||||
eventsToMake = append(
|
||||
eventsToMake, newCreateEvent, newMembershipEvent,
|
||||
eventsToMake, newMembershipEvent,
|
||||
tempPowerLevelsEvent, newJoinRulesEvent,
|
||||
)
|
||||
|
||||
|
@ -467,12 +531,16 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query
|
|||
return eventsToMake, nil
|
||||
}
|
||||
|
||||
func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, senderID spec.SenderID, userDomain spec.ServerName, newRoomID string, newVersion gomatrixserverlib.RoomVersion, eventsToMake []gomatrixserverlib.FledglingEvent) error {
|
||||
func (r *Upgrader) sendInitialEvents(
|
||||
ctx context.Context, evTime time.Time, senderID spec.SenderID, userDomain spec.ServerName, newRoomID string,
|
||||
newVersion gomatrixserverlib.RoomVersion, newCreateEvent gomatrixserverlib.PDU, eventsToMake []gomatrixserverlib.FledglingEvent) error {
|
||||
|
||||
var err error
|
||||
var builtEvents []*types.HeaderedEvent
|
||||
authEvents, _ := gomatrixserverlib.NewAuthEvents(nil)
|
||||
builtEvents = append(builtEvents, &types.HeaderedEvent{PDU: newCreateEvent})
|
||||
authEvents, _ := gomatrixserverlib.NewAuthEvents([]gomatrixserverlib.PDU{newCreateEvent})
|
||||
for i, e := range eventsToMake {
|
||||
depth := i + 1 // depth starts at 1
|
||||
depth := i + 2 // depth starts at 2 since we made the create event already.
|
||||
|
||||
proto := gomatrixserverlib.ProtoEvent{
|
||||
SenderID: string(senderID),
|
||||
|
@ -485,9 +553,7 @@ func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, send
|
|||
if err != nil {
|
||||
return fmt.Errorf("failed to set content of new %q event: %w", proto.Type, err)
|
||||
}
|
||||
if i > 0 {
|
||||
proto.PrevEvents = []string{builtEvents[i-1].EventID()}
|
||||
}
|
||||
proto.PrevEvents = []string{builtEvents[i].EventID()}
|
||||
|
||||
var verImpl gomatrixserverlib.IRoomVersion
|
||||
verImpl, err = gomatrixserverlib.GetRoomVersion(newVersion)
|
||||
|
@ -503,13 +569,12 @@ func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, send
|
|||
event, err = builder.Build(evTime, userDomain, r.Cfg.Matrix.KeyID, r.Cfg.Matrix.PrivateKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build new %q event: %w", builder.Type, err)
|
||||
|
||||
}
|
||||
|
||||
if err = gomatrixserverlib.Allowed(event, authEvents, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
||||
return r.URSAPI.QueryUserIDForSender(ctx, roomID, senderID)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("Failed to auth new %q event: %w", builder.Type, err)
|
||||
return fmt.Errorf("Failed to auth new initial %q event: %w", builder.Type, err)
|
||||
}
|
||||
|
||||
// Add the event to the list of auth events
|
||||
|
@ -599,7 +664,7 @@ func (r *Upgrader) makeHeaderedEvent(ctx context.Context, evTime time.Time, send
|
|||
return headeredEvent, nil
|
||||
}
|
||||
|
||||
func createTemporaryPowerLevels(powerLevelContent *gomatrixserverlib.PowerLevelContent, senderID spec.SenderID) (gomatrixserverlib.FledglingEvent, bool) {
|
||||
func createTemporaryPowerLevels(roomVersion gomatrixserverlib.IRoomVersion, powerLevelContent *gomatrixserverlib.PowerLevelContent, senderID spec.SenderID, creators []string) (gomatrixserverlib.FledglingEvent, bool) {
|
||||
// Work out what power level we need in order to be able to send events
|
||||
// of all types into the room.
|
||||
neededPowerLevel := powerLevelContent.StateDefault
|
||||
|
@ -619,14 +684,20 @@ func createTemporaryPowerLevels(powerLevelContent *gomatrixserverlib.PowerLevelC
|
|||
// so that we can modify them without modifying the original.
|
||||
tempPowerLevelContent.Users = make(map[string]int64, len(powerLevelContent.Users))
|
||||
for key, value := range powerLevelContent.Users {
|
||||
if roomVersion.PrivilegedCreators() && slices.Contains(creators, key) {
|
||||
continue // don't set the creator in the users map!
|
||||
}
|
||||
tempPowerLevelContent.Users[key] = value
|
||||
}
|
||||
|
||||
// If the user who is upgrading the room doesn't already have sufficient
|
||||
// power, then elevate their power levels.
|
||||
if tempPowerLevelContent.UserLevel(senderID) < neededPowerLevel {
|
||||
tempPowerLevelContent.Users[string(senderID)] = neededPowerLevel
|
||||
powerLevelsOverridden = true
|
||||
// the upgrader will be the creator so is guaranteed to have enough perms to do this.
|
||||
if !roomVersion.PrivilegedCreators() {
|
||||
// If the user who is upgrading the room doesn't already have sufficient
|
||||
// power, then elevate their power levels.
|
||||
if tempPowerLevelContent.UserLevel(senderID) < neededPowerLevel {
|
||||
tempPowerLevelContent.Users[string(senderID)] = neededPowerLevel
|
||||
powerLevelsOverridden = true
|
||||
}
|
||||
}
|
||||
|
||||
// Then return the temporary power levels event.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue