Added /upgrade endpoint (#2307)
* Added /upgrade endpoint * fix * Fix lints * More lint lifex * Move room upgrading to the roomserver * Remove extraneous arg * Fix HTTP API for `PerformUpgrade` * Reduce number of API calls in `generateInitialEvents`, preserve membership fields * Refactor `generateInitialEvents` to preserve old state events for all but the essential room setup events * Handle ban events in the state transfer * Refactor and comment `createTemporaryPowerLevels` * Only send two power levels if we needed to override the levels, preserve miscellaneous fields in the create event * Fix copyrights * Review comments @S7evinK * Update sytest whitelist * Specify empty state keys, use `EventLevel`, remove unnecessary check on state copy * Add comment to `restrictOldRoomPowerLevels` * Ensure canonical aliases exist before clearing * Copy invites as well as bans * Fix return error on `m.room.tombstone` handling in client API * Relax checks for well-formedness of join rules, membership event etc Co-authored-by: Alex Kursell <alex@awk.run> Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com> Co-authored-by: kegsay <kegan@matrix.org>
This commit is contained in:
parent
562d742240
commit
2defc4249d
|
@ -957,6 +957,16 @@ func Setup(
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
|
||||||
|
v3mux.Handle("/rooms/{roomID}/upgrade",
|
||||||
|
httputil.MakeAuthAPI("rooms_upgrade", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
if err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
return UpgradeRoom(req, device, cfg, vars["roomID"], userAPI, rsAPI, asAPI)
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
|
||||||
v3mux.Handle("/devices",
|
v3mux.Handle("/devices",
|
||||||
httputil.MakeAuthAPI("get_devices", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("get_devices", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
return GetDevicesByLocalpart(req, userAPI, device)
|
return GetDevicesByLocalpart(req, userAPI, device)
|
||||||
|
|
|
@ -272,5 +272,24 @@ func generateSendEvent(
|
||||||
JSON: jsonerror.Forbidden(err.Error()), // TODO: Is this error string comprehensible to the client?
|
JSON: jsonerror.Forbidden(err.Error()), // TODO: Is this error string comprehensible to the client?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// User should not be able to send a tombstone event to the same room.
|
||||||
|
if e.Type() == "m.room.tombstone" {
|
||||||
|
content := make(map[string]interface{})
|
||||||
|
if err = json.Unmarshal(e.Content(), &content); err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("Cannot unmarshal the event content.")
|
||||||
|
return nil, &util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.BadJSON("Cannot unmarshal the event content."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if content["replacement_room"] == e.RoomID() {
|
||||||
|
return nil, &util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.InvalidParam("Cannot send tombstone event that points to the same room."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return e.Event, nil
|
return e.Event, nil
|
||||||
}
|
}
|
||||||
|
|
92
clientapi/routing/upgrade_room.go
Normal file
92
clientapi/routing/upgrade_room.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package routing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/version"
|
||||||
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type upgradeRoomRequest struct {
|
||||||
|
NewVersion string `json:"new_version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type upgradeRoomResponse struct {
|
||||||
|
ReplacementRoom string `json:"replacement_room"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpgradeRoom implements /upgrade
|
||||||
|
func UpgradeRoom(
|
||||||
|
req *http.Request, device *userapi.Device,
|
||||||
|
cfg *config.ClientAPI,
|
||||||
|
roomID string, profileAPI userapi.UserProfileAPI,
|
||||||
|
rsAPI roomserverAPI.RoomserverInternalAPI,
|
||||||
|
asAPI appserviceAPI.AppServiceQueryAPI,
|
||||||
|
) util.JSONResponse {
|
||||||
|
var r upgradeRoomRequest
|
||||||
|
if rErr := httputil.UnmarshalJSONRequest(req, &r); rErr != nil {
|
||||||
|
return *rErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that the room version is supported
|
||||||
|
if _, err := version.SupportedRoomVersion(gomatrixserverlib.RoomVersion(r.NewVersion)); err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.UnsupportedRoomVersion("This server does not support that room version"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
upgradeReq := roomserverAPI.PerformRoomUpgradeRequest{
|
||||||
|
UserID: device.UserID,
|
||||||
|
RoomID: roomID,
|
||||||
|
RoomVersion: gomatrixserverlib.RoomVersion(r.NewVersion),
|
||||||
|
}
|
||||||
|
upgradeResp := roomserverAPI.PerformRoomUpgradeResponse{}
|
||||||
|
|
||||||
|
rsAPI.PerformRoomUpgrade(req.Context(), &upgradeReq, &upgradeResp)
|
||||||
|
|
||||||
|
if upgradeResp.Error != nil {
|
||||||
|
if upgradeResp.Error.Code == roomserverAPI.PerformErrorNoRoom {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
JSON: jsonerror.NotFound("Room does not exist"),
|
||||||
|
}
|
||||||
|
} else if upgradeResp.Error.Code == roomserverAPI.PerformErrorNotAllowed {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: jsonerror.Forbidden(upgradeResp.Error.Msg),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: upgradeRoomResponse{
|
||||||
|
ReplacementRoom: upgradeResp.NewRoomID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -170,6 +170,9 @@ type RoomserverInternalAPI interface {
|
||||||
// PerformForget forgets a rooms history for a specific user
|
// PerformForget forgets a rooms history for a specific user
|
||||||
PerformForget(ctx context.Context, req *PerformForgetRequest, resp *PerformForgetResponse) error
|
PerformForget(ctx context.Context, req *PerformForgetRequest, resp *PerformForgetResponse) error
|
||||||
|
|
||||||
|
// PerformRoomUpgrade upgrades a room to a newer version
|
||||||
|
PerformRoomUpgrade(ctx context.Context, req *PerformRoomUpgradeRequest, resp *PerformRoomUpgradeResponse)
|
||||||
|
|
||||||
// Asks for the default room version as preferred by the server.
|
// Asks for the default room version as preferred by the server.
|
||||||
QueryRoomVersionCapabilities(
|
QueryRoomVersionCapabilities(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
|
|
@ -67,6 +67,15 @@ func (t *RoomserverInternalAPITrace) PerformUnpeek(
|
||||||
util.GetLogger(ctx).Infof("PerformUnpeek req=%+v res=%+v", js(req), js(res))
|
util.GetLogger(ctx).Infof("PerformUnpeek req=%+v res=%+v", js(req), js(res))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *RoomserverInternalAPITrace) PerformRoomUpgrade(
|
||||||
|
ctx context.Context,
|
||||||
|
req *PerformRoomUpgradeRequest,
|
||||||
|
res *PerformRoomUpgradeResponse,
|
||||||
|
) {
|
||||||
|
t.Impl.PerformRoomUpgrade(ctx, req, res)
|
||||||
|
util.GetLogger(ctx).Infof("PerformRoomUpgrade req=%+v res=%+v", js(req), js(res))
|
||||||
|
}
|
||||||
|
|
||||||
func (t *RoomserverInternalAPITrace) PerformJoin(
|
func (t *RoomserverInternalAPITrace) PerformJoin(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *PerformJoinRequest,
|
req *PerformJoinRequest,
|
||||||
|
|
|
@ -203,3 +203,14 @@ type PerformForgetRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type PerformForgetResponse struct{}
|
type PerformForgetResponse struct{}
|
||||||
|
|
||||||
|
type PerformRoomUpgradeRequest struct {
|
||||||
|
RoomID string `json:"room_id"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PerformRoomUpgradeResponse struct {
|
||||||
|
NewRoomID string
|
||||||
|
Error *PerformError
|
||||||
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ type RoomserverInternalAPI struct {
|
||||||
*perform.Publisher
|
*perform.Publisher
|
||||||
*perform.Backfiller
|
*perform.Backfiller
|
||||||
*perform.Forgetter
|
*perform.Forgetter
|
||||||
|
*perform.Upgrader
|
||||||
ProcessContext *process.ProcessContext
|
ProcessContext *process.ProcessContext
|
||||||
DB storage.Database
|
DB storage.Database
|
||||||
Cfg *config.RoomServer
|
Cfg *config.RoomServer
|
||||||
|
@ -159,6 +160,10 @@ func (r *RoomserverInternalAPI) SetFederationAPI(fsAPI fsAPI.FederationInternalA
|
||||||
r.Forgetter = &perform.Forgetter{
|
r.Forgetter = &perform.Forgetter{
|
||||||
DB: r.DB,
|
DB: r.DB,
|
||||||
}
|
}
|
||||||
|
r.Upgrader = &perform.Upgrader{
|
||||||
|
Cfg: r.Cfg,
|
||||||
|
URSAPI: r,
|
||||||
|
}
|
||||||
|
|
||||||
if err := r.Inputer.Start(); err != nil {
|
if err := r.Inputer.Start(); err != nil {
|
||||||
logrus.WithError(err).Panic("failed to start roomserver input API")
|
logrus.WithError(err).Panic("failed to start roomserver input API")
|
||||||
|
|
709
roomserver/internal/perform/perform_upgrade.go
Normal file
709
roomserver/internal/perform/perform_upgrade.go
Normal file
|
@ -0,0 +1,709 @@
|
||||||
|
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package perform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Upgrader struct {
|
||||||
|
Cfg *config.RoomServer
|
||||||
|
URSAPI api.RoomserverInternalAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
// fledglingEvent is a helper representation of an event used when creating many events in succession.
|
||||||
|
type fledglingEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
StateKey string `json:"state_key"`
|
||||||
|
Content interface{} `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PerformRoomUpgrade upgrades a room from one version to another
|
||||||
|
func (r *Upgrader) PerformRoomUpgrade(
|
||||||
|
ctx context.Context,
|
||||||
|
req *api.PerformRoomUpgradeRequest,
|
||||||
|
res *api.PerformRoomUpgradeResponse,
|
||||||
|
) {
|
||||||
|
res.NewRoomID, res.Error = r.performRoomUpgrade(ctx, req)
|
||||||
|
if res.Error != nil {
|
||||||
|
res.NewRoomID = ""
|
||||||
|
logrus.WithContext(ctx).WithError(res.Error).Error("Room upgrade failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Upgrader) performRoomUpgrade(
|
||||||
|
ctx context.Context,
|
||||||
|
req *api.PerformRoomUpgradeRequest,
|
||||||
|
) (string, *api.PerformError) {
|
||||||
|
roomID := req.RoomID
|
||||||
|
userID := req.UserID
|
||||||
|
evTime := time.Now()
|
||||||
|
|
||||||
|
// Return an immediate error if the room does not exist
|
||||||
|
if err := r.validateRoomExists(ctx, roomID); err != nil {
|
||||||
|
return "", &api.PerformError{
|
||||||
|
Code: api.PerformErrorNoRoom,
|
||||||
|
Msg: "Error validating that the room exists",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Check if the user is authorized to actually perform the upgrade (can send m.room.tombstone)
|
||||||
|
if !r.userIsAuthorized(ctx, userID, roomID) {
|
||||||
|
return "", &api.PerformError{
|
||||||
|
Code: api.PerformErrorNotAllowed,
|
||||||
|
Msg: "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), r.Cfg.Matrix.ServerName)
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return "", &api.PerformError{
|
||||||
|
Msg: fmt.Sprintf("Failed to get latest state: %s", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the tombstone event
|
||||||
|
tombstoneEvent, pErr := r.makeTombstoneEvent(ctx, evTime, userID, roomID, newRoomID)
|
||||||
|
if pErr != nil {
|
||||||
|
return "", pErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, userID, roomID, string(req.RoomVersion), tombstoneEvent)
|
||||||
|
if pErr != nil {
|
||||||
|
return "", pErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Send the tombstone event to the old room (must do this before we set the new canonical_alias)
|
||||||
|
if pErr = r.sendHeaderedEvent(ctx, tombstoneEvent); pErr != nil {
|
||||||
|
return "", pErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the setup events to the new room
|
||||||
|
if pErr = r.sendInitialEvents(ctx, evTime, userID, newRoomID, string(req.RoomVersion), eventsToMake); pErr != nil {
|
||||||
|
return "", pErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the old room was public, make sure the new one is too
|
||||||
|
if pErr = r.publishIfOldRoomWasPublic(ctx, roomID, newRoomID); pErr != nil {
|
||||||
|
return "", pErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the old room had a canonical alias event, it should be deleted in the old room
|
||||||
|
if pErr = r.clearOldCanonicalAliasEvent(ctx, oldRoomRes, evTime, userID, roomID); pErr != nil {
|
||||||
|
return "", pErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Move local aliases to the new room
|
||||||
|
if pErr = moveLocalAliases(ctx, roomID, newRoomID, userID, r.URSAPI); pErr != nil {
|
||||||
|
return "", pErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Restrict power levels in the old room
|
||||||
|
if pErr = r.restrictOldRoomPowerLevels(ctx, evTime, userID, roomID); pErr != nil {
|
||||||
|
return "", pErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRoomID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Upgrader) getRoomPowerLevels(ctx context.Context, roomID string) (*gomatrixserverlib.PowerLevelContent, *api.PerformError) {
|
||||||
|
oldPowerLevelsEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||||
|
EventType: gomatrixserverlib.MRoomPowerLevels,
|
||||||
|
StateKey: "",
|
||||||
|
})
|
||||||
|
powerLevelContent, err := oldPowerLevelsEvent.PowerLevels()
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error()
|
||||||
|
return nil, &api.PerformError{
|
||||||
|
Msg: "powerLevel event was not actually a power level event",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return powerLevelContent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Upgrader) restrictOldRoomPowerLevels(ctx context.Context, evTime time.Time, userID, roomID string) *api.PerformError {
|
||||||
|
restrictedPowerLevelContent, pErr := r.getRoomPowerLevels(ctx, roomID)
|
||||||
|
if pErr != nil {
|
||||||
|
return pErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// From: https://spec.matrix.org/v1.2/client-server-api/#server-behaviour-16
|
||||||
|
// If possible, the power levels in the old room should also be modified to
|
||||||
|
// prevent sending of events and inviting new users. For example, setting
|
||||||
|
// events_default and invite to the greater of 50 and users_default + 1.
|
||||||
|
restrictedDefaultPowerLevel := int64(50)
|
||||||
|
if restrictedPowerLevelContent.UsersDefault+1 > restrictedDefaultPowerLevel {
|
||||||
|
restrictedDefaultPowerLevel = restrictedPowerLevelContent.UsersDefault + 1
|
||||||
|
}
|
||||||
|
restrictedPowerLevelContent.EventsDefault = restrictedDefaultPowerLevel
|
||||||
|
restrictedPowerLevelContent.Invite = restrictedDefaultPowerLevel
|
||||||
|
|
||||||
|
restrictedPowerLevelsHeadered, resErr := r.makeHeaderedEvent(ctx, evTime, userID, roomID, fledglingEvent{
|
||||||
|
Type: gomatrixserverlib.MRoomPowerLevels,
|
||||||
|
StateKey: "",
|
||||||
|
Content: restrictedPowerLevelContent,
|
||||||
|
})
|
||||||
|
if resErr != nil {
|
||||||
|
if resErr.Code == api.PerformErrorNotAllowed {
|
||||||
|
util.GetLogger(ctx).WithField(logrus.ErrorKey, resErr).Warn("UpgradeRoom: Could not restrict power levels in old room")
|
||||||
|
} else {
|
||||||
|
return resErr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if resErr = r.sendHeaderedEvent(ctx, restrictedPowerLevelsHeadered); resErr != nil {
|
||||||
|
return resErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveLocalAliases(ctx context.Context,
|
||||||
|
roomID, newRoomID, userID string,
|
||||||
|
URSAPI api.RoomserverInternalAPI) *api.PerformError {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
aliasReq := api.GetAliasesForRoomIDRequest{RoomID: roomID}
|
||||||
|
aliasRes := api.GetAliasesForRoomIDResponse{}
|
||||||
|
if err = URSAPI.GetAliasesForRoomID(ctx, &aliasReq, &aliasRes); err != nil {
|
||||||
|
return &api.PerformError{
|
||||||
|
Msg: "Could not get aliases for old room",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, alias := range aliasRes.Aliases {
|
||||||
|
removeAliasReq := api.RemoveRoomAliasRequest{UserID: userID, Alias: alias}
|
||||||
|
removeAliasRes := api.RemoveRoomAliasResponse{}
|
||||||
|
if err = URSAPI.RemoveRoomAlias(ctx, &removeAliasReq, &removeAliasRes); err != nil {
|
||||||
|
return &api.PerformError{
|
||||||
|
Msg: "api.RemoveRoomAlias failed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setAliasReq := api.SetRoomAliasRequest{UserID: userID, Alias: alias, RoomID: newRoomID}
|
||||||
|
setAliasRes := api.SetRoomAliasResponse{}
|
||||||
|
if err = URSAPI.SetRoomAlias(ctx, &setAliasReq, &setAliasRes); err != nil {
|
||||||
|
return &api.PerformError{
|
||||||
|
Msg: "api.SetRoomAlias failed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Upgrader) clearOldCanonicalAliasEvent(ctx context.Context, oldRoom *api.QueryLatestEventsAndStateResponse, evTime time.Time, userID, roomID string) *api.PerformError {
|
||||||
|
for _, event := range oldRoom.StateEvents {
|
||||||
|
if event.Type() != gomatrixserverlib.MRoomCanonicalAlias || !event.StateKeyEquals("") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var aliasContent struct {
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
AltAliases []string `json:"alt_aliases"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(event.Content(), &aliasContent); err != nil {
|
||||||
|
return &api.PerformError{
|
||||||
|
Msg: fmt.Sprintf("Failed to unmarshal canonical aliases: %s", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if aliasContent.Alias == "" && len(aliasContent.AltAliases) == 0 {
|
||||||
|
// There are no canonical aliases to clear, therefore do nothing.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyCanonicalAliasEvent, resErr := r.makeHeaderedEvent(ctx, evTime, userID, roomID, fledglingEvent{
|
||||||
|
Type: gomatrixserverlib.MRoomCanonicalAlias,
|
||||||
|
Content: map[string]interface{}{},
|
||||||
|
})
|
||||||
|
if resErr != nil {
|
||||||
|
if resErr.Code == api.PerformErrorNotAllowed {
|
||||||
|
util.GetLogger(ctx).WithField(logrus.ErrorKey, resErr).Warn("UpgradeRoom: Could not set empty canonical alias event in old room")
|
||||||
|
} else {
|
||||||
|
return resErr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if resErr = r.sendHeaderedEvent(ctx, emptyCanonicalAliasEvent); resErr != nil {
|
||||||
|
return resErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Upgrader) publishIfOldRoomWasPublic(ctx context.Context, roomID, newRoomID string) *api.PerformError {
|
||||||
|
// check if the old room was published
|
||||||
|
var pubQueryRes api.QueryPublishedRoomsResponse
|
||||||
|
err := r.URSAPI.QueryPublishedRooms(ctx, &api.QueryPublishedRoomsRequest{
|
||||||
|
RoomID: roomID,
|
||||||
|
}, &pubQueryRes)
|
||||||
|
if err != nil {
|
||||||
|
return &api.PerformError{
|
||||||
|
Msg: "QueryPublishedRooms failed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the old room is published (was public), publish the new room
|
||||||
|
if len(pubQueryRes.RoomIDs) == 1 {
|
||||||
|
publishNewRoomAndUnpublishOldRoom(ctx, r.URSAPI, roomID, newRoomID)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func publishNewRoomAndUnpublishOldRoom(
|
||||||
|
ctx context.Context,
|
||||||
|
URSAPI api.RoomserverInternalAPI,
|
||||||
|
oldRoomID, newRoomID string,
|
||||||
|
) {
|
||||||
|
// expose this room in the published room list
|
||||||
|
var pubNewRoomRes api.PerformPublishResponse
|
||||||
|
URSAPI.PerformPublish(ctx, &api.PerformPublishRequest{
|
||||||
|
RoomID: newRoomID,
|
||||||
|
Visibility: "public",
|
||||||
|
}, &pubNewRoomRes)
|
||||||
|
if pubNewRoomRes.Error != nil {
|
||||||
|
// treat as non-fatal since the room is already made by this point
|
||||||
|
util.GetLogger(ctx).WithError(pubNewRoomRes.Error).Error("failed to visibility:public")
|
||||||
|
}
|
||||||
|
|
||||||
|
var unpubOldRoomRes api.PerformPublishResponse
|
||||||
|
// remove the old room from the published room list
|
||||||
|
URSAPI.PerformPublish(ctx, &api.PerformPublishRequest{
|
||||||
|
RoomID: oldRoomID,
|
||||||
|
Visibility: "private",
|
||||||
|
}, &unpubOldRoomRes)
|
||||||
|
if unpubOldRoomRes.Error != nil {
|
||||||
|
// treat as non-fatal since the room is already made by this point
|
||||||
|
util.GetLogger(ctx).WithError(unpubOldRoomRes.Error).Error("failed to visibility:private")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Upgrader) validateRoomExists(ctx context.Context, roomID string) error {
|
||||||
|
verReq := api.QueryRoomVersionForRoomRequest{RoomID: roomID}
|
||||||
|
verRes := api.QueryRoomVersionForRoomResponse{}
|
||||||
|
if err := r.URSAPI.QueryRoomVersionForRoom(ctx, &verReq, &verRes); err != nil {
|
||||||
|
return &api.PerformError{
|
||||||
|
Code: api.PerformErrorNoRoom,
|
||||||
|
Msg: "Room does not exist",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Upgrader) userIsAuthorized(ctx context.Context, userID, roomID string,
|
||||||
|
) bool {
|
||||||
|
plEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||||
|
EventType: gomatrixserverlib.MRoomPowerLevels,
|
||||||
|
StateKey: "",
|
||||||
|
})
|
||||||
|
if plEvent == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
pl, err := plEvent.PowerLevels()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// 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(userID) >= pl.EventLevel("m.room.tombstone", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint:gocyclo
|
||||||
|
func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.QueryLatestEventsAndStateResponse, userID, roomID, newVersion string, tombstoneEvent *gomatrixserverlib.HeaderedEvent) ([]fledglingEvent, *api.PerformError) {
|
||||||
|
state := make(map[gomatrixserverlib.StateKeyTuple]*gomatrixserverlib.HeaderedEvent, len(oldRoom.StateEvents))
|
||||||
|
for _, event := range oldRoom.StateEvents {
|
||||||
|
if event.StateKey() == nil {
|
||||||
|
// This shouldn't ever happen, but better to be safe than sorry.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if event.Type() == gomatrixserverlib.MRoomMember && !event.StateKeyEquals(userID) {
|
||||||
|
// With the exception of bans and invites which we do want to copy, we
|
||||||
|
// should ignore membership events that aren't our own, as event auth will
|
||||||
|
// prevent us from being able to create membership events on behalf of other
|
||||||
|
// users anyway unless they are invites or bans.
|
||||||
|
membership, err := event.Membership()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch membership {
|
||||||
|
case gomatrixserverlib.Ban:
|
||||||
|
case gomatrixserverlib.Invite:
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state[gomatrixserverlib.StateKeyTuple{EventType: event.Type(), StateKey: *event.StateKey()}] = event
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following events are ones that we are going to override manually
|
||||||
|
// in the following section.
|
||||||
|
override := map[gomatrixserverlib.StateKeyTuple]struct{}{
|
||||||
|
{EventType: gomatrixserverlib.MRoomCreate, StateKey: ""}: {},
|
||||||
|
{EventType: gomatrixserverlib.MRoomMember, StateKey: userID}: {},
|
||||||
|
{EventType: gomatrixserverlib.MRoomPowerLevels, StateKey: ""}: {},
|
||||||
|
{EventType: gomatrixserverlib.MRoomJoinRules, StateKey: ""}: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// The overridden events are essential events that must be present in the
|
||||||
|
// old room state. Check that they are there.
|
||||||
|
for tuple := range override {
|
||||||
|
if _, ok := state[tuple]; !ok {
|
||||||
|
return nil, &api.PerformError{
|
||||||
|
Msg: fmt.Sprintf("Essential event of type %q state key %q is missing", tuple.EventType, tuple.StateKey),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oldCreateEvent := state[gomatrixserverlib.StateKeyTuple{EventType: gomatrixserverlib.MRoomCreate, StateKey: ""}]
|
||||||
|
oldMembershipEvent := state[gomatrixserverlib.StateKeyTuple{EventType: gomatrixserverlib.MRoomMember, StateKey: userID}]
|
||||||
|
oldPowerLevelsEvent := state[gomatrixserverlib.StateKeyTuple{EventType: gomatrixserverlib.MRoomPowerLevels, StateKey: ""}]
|
||||||
|
oldJoinRulesEvent := state[gomatrixserverlib.StateKeyTuple{EventType: gomatrixserverlib.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)
|
||||||
|
newCreateContent["creator"] = userID
|
||||||
|
newCreateContent["room_version"] = newVersion
|
||||||
|
newCreateContent["predecessor"] = gomatrixserverlib.PreviousRoom{
|
||||||
|
EventID: tombstoneEvent.EventID(),
|
||||||
|
RoomID: roomID,
|
||||||
|
}
|
||||||
|
newCreateEvent := fledglingEvent{
|
||||||
|
Type: gomatrixserverlib.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
|
||||||
|
// the events after it.
|
||||||
|
newMembershipContent := map[string]interface{}{}
|
||||||
|
_ = json.Unmarshal(oldMembershipEvent.Content(), &newMembershipContent)
|
||||||
|
newMembershipContent["membership"] = gomatrixserverlib.Join
|
||||||
|
newMembershipEvent := fledglingEvent{
|
||||||
|
Type: gomatrixserverlib.MRoomMember,
|
||||||
|
StateKey: userID,
|
||||||
|
Content: newMembershipContent,
|
||||||
|
}
|
||||||
|
|
||||||
|
// We might need to temporarily give ourselves a higher power level
|
||||||
|
// than we had in the old room in order to be able to send all of
|
||||||
|
// the relevant state events. This function will return whether we
|
||||||
|
// had to override the power level events or not — if we did, we
|
||||||
|
// need to send the original power levels again later on.
|
||||||
|
powerLevelContent, err := oldPowerLevelsEvent.PowerLevels()
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error()
|
||||||
|
return nil, &api.PerformError{
|
||||||
|
Msg: "Power level event content was invalid",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tempPowerLevelsEvent, powerLevelsOverridden := createTemporaryPowerLevels(powerLevelContent, userID)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// existing join rules contains garbage, the room can still be
|
||||||
|
// upgraded.
|
||||||
|
newJoinRulesContent := map[string]interface{}{
|
||||||
|
"join_rule": gomatrixserverlib.Invite, // sane default
|
||||||
|
}
|
||||||
|
_ = json.Unmarshal(oldJoinRulesEvent.Content(), &newJoinRulesContent)
|
||||||
|
newJoinRulesEvent := fledglingEvent{
|
||||||
|
Type: gomatrixserverlib.MRoomJoinRules,
|
||||||
|
StateKey: "",
|
||||||
|
Content: newJoinRulesContent,
|
||||||
|
}
|
||||||
|
|
||||||
|
eventsToMake := make([]fledglingEvent, 0, len(state))
|
||||||
|
eventsToMake = append(
|
||||||
|
eventsToMake, newCreateEvent, newMembershipEvent,
|
||||||
|
tempPowerLevelsEvent, newJoinRulesEvent,
|
||||||
|
)
|
||||||
|
|
||||||
|
// For some reason Sytest expects there to be a guest access event.
|
||||||
|
// Create one if it doesn't exist.
|
||||||
|
if _, ok := state[gomatrixserverlib.StateKeyTuple{EventType: gomatrixserverlib.MRoomGuestAccess, StateKey: ""}]; !ok {
|
||||||
|
eventsToMake = append(eventsToMake, fledglingEvent{
|
||||||
|
Type: gomatrixserverlib.MRoomGuestAccess,
|
||||||
|
Content: map[string]string{
|
||||||
|
"guest_access": "forbidden",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duplicate all of the old state events into the new room.
|
||||||
|
for tuple, event := range state {
|
||||||
|
if _, ok := override[tuple]; ok {
|
||||||
|
// Don't duplicate events we have overridden already. They
|
||||||
|
// are already in `eventsToMake`.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newEvent := fledglingEvent{
|
||||||
|
Type: tuple.EventType,
|
||||||
|
StateKey: tuple.StateKey,
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(event.Content(), &newEvent.Content); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to unmarshal old event")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
eventsToMake = append(eventsToMake, newEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we sent a temporary power level event into the room before,
|
||||||
|
// override that now by restoring the original power levels.
|
||||||
|
if powerLevelsOverridden {
|
||||||
|
eventsToMake = append(eventsToMake, fledglingEvent{
|
||||||
|
Type: gomatrixserverlib.MRoomPowerLevels,
|
||||||
|
Content: powerLevelContent,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return eventsToMake, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, userID, newRoomID, newVersion string, eventsToMake []fledglingEvent) *api.PerformError {
|
||||||
|
var err error
|
||||||
|
var builtEvents []*gomatrixserverlib.HeaderedEvent
|
||||||
|
authEvents := gomatrixserverlib.NewAuthEvents(nil)
|
||||||
|
for i, e := range eventsToMake {
|
||||||
|
depth := i + 1 // depth starts at 1
|
||||||
|
|
||||||
|
builder := gomatrixserverlib.EventBuilder{
|
||||||
|
Sender: userID,
|
||||||
|
RoomID: newRoomID,
|
||||||
|
Type: e.Type,
|
||||||
|
StateKey: &e.StateKey,
|
||||||
|
Depth: int64(depth),
|
||||||
|
}
|
||||||
|
err = builder.SetContent(e.Content)
|
||||||
|
if err != nil {
|
||||||
|
return &api.PerformError{
|
||||||
|
Msg: "builder.SetContent failed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i > 0 {
|
||||||
|
builder.PrevEvents = []gomatrixserverlib.EventReference{builtEvents[i-1].EventReference()}
|
||||||
|
}
|
||||||
|
var event *gomatrixserverlib.Event
|
||||||
|
event, err = r.buildEvent(&builder, &authEvents, evTime, gomatrixserverlib.RoomVersion(newVersion))
|
||||||
|
if err != nil {
|
||||||
|
return &api.PerformError{
|
||||||
|
Msg: "buildEvent failed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = gomatrixserverlib.Allowed(event, &authEvents); err != nil {
|
||||||
|
return &api.PerformError{
|
||||||
|
Msg: "gomatrixserverlib.Allowed failed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the event to the list of auth events
|
||||||
|
builtEvents = append(builtEvents, event.Headered(gomatrixserverlib.RoomVersion(newVersion)))
|
||||||
|
err = authEvents.AddEvent(event)
|
||||||
|
if err != nil {
|
||||||
|
return &api.PerformError{
|
||||||
|
Msg: "authEvents.AddEvent failed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs := make([]api.InputRoomEvent, 0, len(builtEvents))
|
||||||
|
for _, event := range builtEvents {
|
||||||
|
inputs = append(inputs, api.InputRoomEvent{
|
||||||
|
Kind: api.KindNew,
|
||||||
|
Event: event,
|
||||||
|
Origin: r.Cfg.Matrix.ServerName,
|
||||||
|
SendAsServer: api.DoNotSendToOtherServers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err = api.SendInputRoomEvents(ctx, r.URSAPI, inputs, false); err != nil {
|
||||||
|
return &api.PerformError{
|
||||||
|
Msg: "api.SendInputRoomEvents failed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Upgrader) makeTombstoneEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
evTime time.Time,
|
||||||
|
userID, roomID, newRoomID string,
|
||||||
|
) (*gomatrixserverlib.HeaderedEvent, *api.PerformError) {
|
||||||
|
content := map[string]interface{}{
|
||||||
|
"body": "This room has been replaced",
|
||||||
|
"replacement_room": newRoomID,
|
||||||
|
}
|
||||||
|
event := fledglingEvent{
|
||||||
|
Type: "m.room.tombstone",
|
||||||
|
Content: content,
|
||||||
|
}
|
||||||
|
return r.makeHeaderedEvent(ctx, evTime, userID, roomID, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Upgrader) makeHeaderedEvent(ctx context.Context, evTime time.Time, userID, roomID string, event fledglingEvent) (*gomatrixserverlib.HeaderedEvent, *api.PerformError) {
|
||||||
|
builder := gomatrixserverlib.EventBuilder{
|
||||||
|
Sender: userID,
|
||||||
|
RoomID: roomID,
|
||||||
|
Type: event.Type,
|
||||||
|
StateKey: &event.StateKey,
|
||||||
|
}
|
||||||
|
err := builder.SetContent(event.Content)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &api.PerformError{
|
||||||
|
Msg: "builder.SetContent failed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var queryRes api.QueryLatestEventsAndStateResponse
|
||||||
|
headeredEvent, err := eventutil.QueryAndBuildEvent(ctx, &builder, r.Cfg.Matrix, evTime, r.URSAPI, &queryRes)
|
||||||
|
if err == eventutil.ErrRoomNoExists {
|
||||||
|
return nil, &api.PerformError{
|
||||||
|
Code: api.PerformErrorNoRoom,
|
||||||
|
Msg: "Room does not exist",
|
||||||
|
}
|
||||||
|
} else if e, ok := err.(gomatrixserverlib.BadJSONError); ok {
|
||||||
|
return nil, &api.PerformError{
|
||||||
|
Msg: e.Error(),
|
||||||
|
}
|
||||||
|
} else if e, ok := err.(gomatrixserverlib.EventValidationError); ok {
|
||||||
|
if e.Code == gomatrixserverlib.EventValidationTooLarge {
|
||||||
|
return nil, &api.PerformError{
|
||||||
|
Msg: e.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, &api.PerformError{
|
||||||
|
Msg: e.Error(),
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, &api.PerformError{
|
||||||
|
Msg: "eventutil.BuildEvent failed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check to see if this user can perform this operation
|
||||||
|
stateEvents := make([]*gomatrixserverlib.Event, len(queryRes.StateEvents))
|
||||||
|
for i := range queryRes.StateEvents {
|
||||||
|
stateEvents[i] = queryRes.StateEvents[i].Event
|
||||||
|
}
|
||||||
|
provider := gomatrixserverlib.NewAuthEvents(stateEvents)
|
||||||
|
if err = gomatrixserverlib.Allowed(headeredEvent.Event, &provider); err != nil {
|
||||||
|
return nil, &api.PerformError{
|
||||||
|
Code: api.PerformErrorNotAllowed,
|
||||||
|
Msg: err.Error(), // TODO: Is this error string comprehensible to the client?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return headeredEvent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTemporaryPowerLevels(powerLevelContent *gomatrixserverlib.PowerLevelContent, userID string) (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
|
||||||
|
for _, powerLevel := range powerLevelContent.Events {
|
||||||
|
if powerLevel > neededPowerLevel {
|
||||||
|
neededPowerLevel = powerLevel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a copy of the existing power level content.
|
||||||
|
tempPowerLevelContent := *powerLevelContent
|
||||||
|
powerLevelsOverridden := false
|
||||||
|
|
||||||
|
// At this point, the "Users", "Events" and "Notifications" keys are all
|
||||||
|
// pointing to the map of the original PL content, so we will specifically
|
||||||
|
// override the users map with a new one and duplicate the values deeply,
|
||||||
|
// 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 {
|
||||||
|
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(userID) < neededPowerLevel {
|
||||||
|
tempPowerLevelContent.Users[userID] = neededPowerLevel
|
||||||
|
powerLevelsOverridden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then return the temporary power levels event.
|
||||||
|
return fledglingEvent{
|
||||||
|
Type: gomatrixserverlib.MRoomPowerLevels,
|
||||||
|
Content: tempPowerLevelContent,
|
||||||
|
}, powerLevelsOverridden
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Upgrader) sendHeaderedEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
headeredEvent *gomatrixserverlib.HeaderedEvent,
|
||||||
|
) *api.PerformError {
|
||||||
|
var inputs []api.InputRoomEvent
|
||||||
|
inputs = append(inputs, api.InputRoomEvent{
|
||||||
|
Kind: api.KindNew,
|
||||||
|
Event: headeredEvent,
|
||||||
|
Origin: r.Cfg.Matrix.ServerName,
|
||||||
|
SendAsServer: api.DoNotSendToOtherServers,
|
||||||
|
})
|
||||||
|
if err := api.SendInputRoomEvents(ctx, r.URSAPI, inputs, false); err != nil {
|
||||||
|
return &api.PerformError{
|
||||||
|
Msg: "api.SendInputRoomEvents failed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Upgrader) buildEvent(
|
||||||
|
builder *gomatrixserverlib.EventBuilder,
|
||||||
|
provider gomatrixserverlib.AuthEventProvider,
|
||||||
|
evTime time.Time,
|
||||||
|
roomVersion gomatrixserverlib.RoomVersion,
|
||||||
|
) (*gomatrixserverlib.Event, error) {
|
||||||
|
eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(builder)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
refs, err := eventsNeeded.AuthEventReferences(provider)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
builder.AuthEvents = refs
|
||||||
|
event, err := builder.Build(
|
||||||
|
evTime, r.Cfg.Matrix.ServerName, r.Cfg.Matrix.KeyID,
|
||||||
|
r.Cfg.Matrix.PrivateKey, roomVersion,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot build event %s : Builder failed to build. %w", builder.Type, err)
|
||||||
|
}
|
||||||
|
return event, nil
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ const (
|
||||||
RoomserverPerformInvitePath = "/roomserver/performInvite"
|
RoomserverPerformInvitePath = "/roomserver/performInvite"
|
||||||
RoomserverPerformPeekPath = "/roomserver/performPeek"
|
RoomserverPerformPeekPath = "/roomserver/performPeek"
|
||||||
RoomserverPerformUnpeekPath = "/roomserver/performUnpeek"
|
RoomserverPerformUnpeekPath = "/roomserver/performUnpeek"
|
||||||
|
RoomserverPerformRoomUpgradePath = "/roomserver/performRoomUpgrade"
|
||||||
RoomserverPerformJoinPath = "/roomserver/performJoin"
|
RoomserverPerformJoinPath = "/roomserver/performJoin"
|
||||||
RoomserverPerformLeavePath = "/roomserver/performLeave"
|
RoomserverPerformLeavePath = "/roomserver/performLeave"
|
||||||
RoomserverPerformBackfillPath = "/roomserver/performBackfill"
|
RoomserverPerformBackfillPath = "/roomserver/performBackfill"
|
||||||
|
@ -252,6 +253,23 @@ func (h *httpRoomserverInternalAPI) PerformUnpeek(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *httpRoomserverInternalAPI) PerformRoomUpgrade(
|
||||||
|
ctx context.Context,
|
||||||
|
request *api.PerformRoomUpgradeRequest,
|
||||||
|
response *api.PerformRoomUpgradeResponse,
|
||||||
|
) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "PerformRoomUpgrade")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
|
apiURL := h.roomserverURL + RoomserverPerformRoomUpgradePath
|
||||||
|
err := httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
|
||||||
|
if err != nil {
|
||||||
|
response.Error = &api.PerformError{
|
||||||
|
Msg: fmt.Sprintf("failed to communicate with roomserver: %s", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (h *httpRoomserverInternalAPI) PerformLeave(
|
func (h *httpRoomserverInternalAPI) PerformLeave(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request *api.PerformLeaveRequest,
|
request *api.PerformLeaveRequest,
|
||||||
|
|
|
@ -96,6 +96,17 @@ func AddRoutes(r api.RoomserverInternalAPI, internalAPIMux *mux.Router) {
|
||||||
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
internalAPIMux.Handle(RoomserverPerformRoomUpgradePath,
|
||||||
|
httputil.MakeInternalAPI("performRoomUpgrade", func(req *http.Request) util.JSONResponse {
|
||||||
|
var request api.PerformRoomUpgradeRequest
|
||||||
|
var response api.PerformRoomUpgradeResponse
|
||||||
|
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
|
||||||
|
return util.MessageResponse(http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
r.PerformRoomUpgrade(req.Context(), &request, &response)
|
||||||
|
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
||||||
|
}),
|
||||||
|
)
|
||||||
internalAPIMux.Handle(RoomserverPerformPublishPath,
|
internalAPIMux.Handle(RoomserverPerformPublishPath,
|
||||||
httputil.MakeInternalAPI("performPublish", func(req *http.Request) util.JSONResponse {
|
httputil.MakeInternalAPI("performPublish", func(req *http.Request) util.JSONResponse {
|
||||||
var request api.PerformPublishRequest
|
var request api.PerformPublishRequest
|
||||||
|
|
|
@ -661,4 +661,22 @@ Canonical alias can include alt_aliases
|
||||||
Can delete canonical alias
|
Can delete canonical alias
|
||||||
AS can make room aliases
|
AS can make room aliases
|
||||||
/context/ with lazy_load_members filter works
|
/context/ with lazy_load_members filter works
|
||||||
|
/upgrade creates a new room
|
||||||
|
/upgrade should preserve room visibility for public rooms
|
||||||
|
/upgrade should preserve room visibility for private rooms
|
||||||
|
/upgrade copies the power levels to the new room
|
||||||
|
/upgrade preserves the power level of the upgrading user in old and new rooms
|
||||||
|
/upgrade copies important state to the new room
|
||||||
|
/upgrade copies ban events to the new room
|
||||||
|
local user has push rules copied to upgraded room
|
||||||
|
remote user has push rules copied to upgraded room
|
||||||
|
/upgrade moves aliases to the new room
|
||||||
|
/upgrade preserves room federation ability
|
||||||
|
/upgrade restricts power levels in the old room
|
||||||
|
/upgrade restricts power levels in the old room when the old PLs are unusual
|
||||||
|
/upgrade to an unknown version is rejected
|
||||||
|
/upgrade is rejected if the user can't send state events
|
||||||
|
/upgrade of a bogus room fails gracefully
|
||||||
|
Cannot send tombstone event that points to the same room
|
||||||
Room summary counts change when membership changes
|
Room summary counts change when membership changes
|
||||||
|
/upgrade copies >100 power levels to the new room
|
||||||
|
|
Loading…
Reference in a new issue