mirror of
https://github.com/matrix-org/dendrite.git
synced 2026-01-01 11:13:12 -06:00
Move room upgrading to the roomserver
This commit is contained in:
parent
cf3b613200
commit
e799577340
|
|
@ -15,22 +15,17 @@
|
|||
package routing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||
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"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type upgradeRoomRequest struct {
|
||||
|
|
@ -42,7 +37,6 @@ type upgradeRoomResponse struct {
|
|||
}
|
||||
|
||||
// UpgradeRoom implements /upgrade
|
||||
// nolint: gocyclo
|
||||
func UpgradeRoom(
|
||||
req *http.Request, device *userapi.Device,
|
||||
cfg *config.ClientAPI,
|
||||
|
|
@ -50,699 +44,49 @@ func UpgradeRoom(
|
|||
rsAPI roomserverAPI.RoomserverInternalAPI,
|
||||
asAPI appserviceAPI.AppServiceQueryAPI,
|
||||
) util.JSONResponse {
|
||||
evTime, err := httputil.ParseTSParam(req)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.InvalidArgumentValue(err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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"),
|
||||
}
|
||||
}
|
||||
|
||||
// Return an immediate error if the room does not exist
|
||||
verReq := roomserverAPI.QueryRoomVersionForRoomRequest{RoomID: roomID}
|
||||
verRes := roomserverAPI.QueryRoomVersionForRoomResponse{}
|
||||
if err = rsAPI.QueryRoomVersionForRoom(req.Context(), &verReq, &verRes); err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound("Room does not exist"),
|
||||
}
|
||||
upgradeReq := roomserverAPI.PerformRoomUpgradeRequest{
|
||||
UserID: device.UserID,
|
||||
RoomID: roomID,
|
||||
RoomVersion: gomatrixserverlib.RoomVersion(r.NewVersion),
|
||||
}
|
||||
upgradeResp := roomserverAPI.PerformRoomUpgradeResponse{}
|
||||
|
||||
// 1. Check if the user is authorized to actually perform the upgrade (can send m.room.tombstone)
|
||||
if rErr := userIsAuthorized(req, device, roomID, rsAPI); rErr != nil {
|
||||
return *rErr
|
||||
}
|
||||
rsAPI.PerformRoomUpgrade(req.Context(), &upgradeReq, &upgradeResp)
|
||||
|
||||
oldCreateEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomCreate,
|
||||
StateKey: "",
|
||||
})
|
||||
oldPowerLevelsEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomPowerLevels,
|
||||
StateKey: "",
|
||||
})
|
||||
oldJoinRulesEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomJoinRules,
|
||||
StateKey: "",
|
||||
})
|
||||
oldHistoryVisibilityEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomHistoryVisibility,
|
||||
StateKey: "",
|
||||
})
|
||||
oldNameEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomName,
|
||||
StateKey: "",
|
||||
})
|
||||
oldTopicEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomTopic,
|
||||
StateKey: "",
|
||||
})
|
||||
oldGuestAccessEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomGuestAccess,
|
||||
StateKey: "",
|
||||
})
|
||||
oldAvatarEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomAvatar,
|
||||
StateKey: "",
|
||||
})
|
||||
oldEncryptionEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomEncryption,
|
||||
StateKey: "",
|
||||
})
|
||||
oldServerAclEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: "m.room.server_acl",
|
||||
StateKey: "",
|
||||
})
|
||||
// Not in the spec, but needed for sytest compatablility
|
||||
oldRelatedGroupsEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: "m.room.related_groups",
|
||||
StateKey: "",
|
||||
})
|
||||
oldCanonicalAliasEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomCanonicalAlias,
|
||||
StateKey: "",
|
||||
})
|
||||
|
||||
// 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), cfg.Matrix.ServerName)
|
||||
userID := device.UserID
|
||||
profile, err := appserviceAPI.RetrieveUserProfile(req.Context(), userID, asAPI, profileAPI)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("appserviceAPI.RetrieveUserProfile failed")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
||||
// Make the tombstone event
|
||||
tombstoneEvent, resErr := makeTombstoneEvent(req, device, cfg, evTime, roomID, newRoomID, rsAPI)
|
||||
if err != nil {
|
||||
return *resErr
|
||||
}
|
||||
|
||||
newCreateContent := map[string]interface{}{
|
||||
"creator": userID,
|
||||
"room_version": r.NewVersion, // TODO: change struct to single var?
|
||||
"predecessor": gomatrixserverlib.PreviousRoom{
|
||||
EventID: tombstoneEvent.EventID(),
|
||||
RoomID: roomID,
|
||||
},
|
||||
}
|
||||
oldCreateContent := unmarshal(oldCreateEvent.Content())
|
||||
if federate, ok := oldCreateContent["m.federate"].(bool); ok {
|
||||
newCreateContent["m.federate"] = federate
|
||||
}
|
||||
|
||||
newCreateEvent := fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomCreate,
|
||||
Content: newCreateContent,
|
||||
}
|
||||
|
||||
membershipEvent := fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomMember,
|
||||
StateKey: userID,
|
||||
Content: gomatrixserverlib.MemberContent{
|
||||
Membership: gomatrixserverlib.Join,
|
||||
DisplayName: profile.DisplayName,
|
||||
AvatarURL: profile.AvatarURL,
|
||||
},
|
||||
}
|
||||
|
||||
powerLevelContent, err := oldPowerLevelsEvent.PowerLevels()
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("powerLevel event was not actually a power level event")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
||||
newPowerLevelsEvent := fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomPowerLevels,
|
||||
Content: powerLevelContent,
|
||||
}
|
||||
|
||||
//create temporary power level event that elevates upgrading user's prvileges to create every copied state event
|
||||
tempPowerLevelsEvent := createTemporaryPowerLevels(powerLevelContent, userID)
|
||||
|
||||
joinRulesContent, err := oldJoinRulesEvent.JoinRule()
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("Join rules event had bad content")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
newJoinRulesEvent := fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomJoinRules,
|
||||
Content: map[string]interface{}{
|
||||
"join_rule": joinRulesContent,
|
||||
},
|
||||
}
|
||||
|
||||
historyVisibilityContent, err := oldHistoryVisibilityEvent.HistoryVisibility()
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("History visibility event had bad content")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
newHistoryVisibilityEvent := fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomHistoryVisibility,
|
||||
Content: map[string]interface{}{
|
||||
"history_visibility": historyVisibilityContent,
|
||||
},
|
||||
}
|
||||
|
||||
var newNameEvent fledglingEvent
|
||||
var newTopicEvent fledglingEvent
|
||||
var newGuestAccessEvent fledglingEvent
|
||||
var newAvatarEvent fledglingEvent
|
||||
var newEncryptionEvent fledglingEvent
|
||||
var newServerACLEvent fledglingEvent
|
||||
var newRelatedGroupsEvent fledglingEvent
|
||||
var newCanonicalAliasEvent fledglingEvent
|
||||
|
||||
if oldNameEvent != nil {
|
||||
newNameEvent = fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomName,
|
||||
Content: unmarshal(oldNameEvent.Content()),
|
||||
}
|
||||
}
|
||||
if oldTopicEvent != nil {
|
||||
newTopicEvent = fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomTopic,
|
||||
Content: unmarshal(oldTopicEvent.Content()),
|
||||
}
|
||||
}
|
||||
if oldGuestAccessEvent != nil {
|
||||
newGuestAccessEvent = fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomGuestAccess,
|
||||
Content: unmarshal(oldGuestAccessEvent.Content()),
|
||||
}
|
||||
}
|
||||
if oldAvatarEvent != nil {
|
||||
newAvatarEvent = fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomAvatar,
|
||||
Content: unmarshal(oldAvatarEvent.Content()),
|
||||
}
|
||||
}
|
||||
if oldEncryptionEvent != nil {
|
||||
newEncryptionEvent = fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomEncryption,
|
||||
Content: unmarshal(oldEncryptionEvent.Content()),
|
||||
}
|
||||
}
|
||||
if oldServerAclEvent != nil {
|
||||
newServerACLEvent = fledglingEvent{
|
||||
Type: "m.room.server_acl",
|
||||
Content: unmarshal(oldServerAclEvent.Content()),
|
||||
}
|
||||
}
|
||||
if oldRelatedGroupsEvent != nil {
|
||||
newRelatedGroupsEvent = fledglingEvent{
|
||||
Type: "m.room.related_groups",
|
||||
Content: unmarshal(oldRelatedGroupsEvent.Content()),
|
||||
}
|
||||
}
|
||||
if oldCanonicalAliasEvent != nil {
|
||||
newCanonicalAliasEvent = fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomCanonicalAlias,
|
||||
Content: unmarshal(oldCanonicalAliasEvent.Content()),
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Replicate transferable state events
|
||||
// send events into the room in order of:
|
||||
// 1- m.room.create
|
||||
// 2- m.room.power_levels (temporary, to allow the upgrading user to send everything)
|
||||
// 3- m.room.join_rules
|
||||
// 4- m.room.history_visibility
|
||||
// 5- m.room.guest_access
|
||||
// 6- m.room.name
|
||||
// 7- m.room.avatar
|
||||
// 8- m.room.topic
|
||||
// 9- m.room.encryption
|
||||
// 10-m.room.server_acl
|
||||
// 11-m.room.related_groups
|
||||
// 12-m.room.canonical_alias
|
||||
// 13-All ban events from the old room
|
||||
// 14-The original room power levels
|
||||
eventsToMake := []fledglingEvent{
|
||||
newCreateEvent, membershipEvent, tempPowerLevelsEvent, newJoinRulesEvent, newHistoryVisibilityEvent,
|
||||
}
|
||||
if oldGuestAccessEvent != nil {
|
||||
eventsToMake = append(eventsToMake, newGuestAccessEvent)
|
||||
} else { // Always create this with the default value to appease sytests
|
||||
eventsToMake = append(eventsToMake, fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomGuestAccess,
|
||||
Content: map[string]interface{}{"guest_access": "forbidden"},
|
||||
})
|
||||
}
|
||||
if oldNameEvent != nil {
|
||||
eventsToMake = append(eventsToMake, newNameEvent)
|
||||
}
|
||||
if oldAvatarEvent != nil {
|
||||
eventsToMake = append(eventsToMake, newAvatarEvent)
|
||||
}
|
||||
if oldTopicEvent != nil {
|
||||
eventsToMake = append(eventsToMake, newTopicEvent)
|
||||
}
|
||||
if oldEncryptionEvent != nil {
|
||||
eventsToMake = append(eventsToMake, newEncryptionEvent)
|
||||
}
|
||||
if oldServerAclEvent != nil {
|
||||
eventsToMake = append(eventsToMake, newServerACLEvent)
|
||||
}
|
||||
if oldRelatedGroupsEvent != nil {
|
||||
eventsToMake = append(eventsToMake, newRelatedGroupsEvent)
|
||||
}
|
||||
if oldCanonicalAliasEvent != nil {
|
||||
eventsToMake = append(eventsToMake, newCanonicalAliasEvent)
|
||||
}
|
||||
banEvents, err := getBanEvents(req, roomID, rsAPI)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("QueryCurrentState failed")
|
||||
return jsonerror.InternalServerError()
|
||||
} else {
|
||||
eventsToMake = append(eventsToMake, banEvents...)
|
||||
}
|
||||
eventsToMake = append(eventsToMake, newPowerLevelsEvent)
|
||||
|
||||
// 5. Send the tombstone event to the old room (must do this before we set the new canonical_alias)
|
||||
resErr = sendHeaderedEvent(req, cfg, rsAPI, tombstoneEvent)
|
||||
if resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
|
||||
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 {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("builder.SetContent failed")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
if i > 0 {
|
||||
builder.PrevEvents = []gomatrixserverlib.EventReference{builtEvents[i-1].EventReference()}
|
||||
}
|
||||
var event *gomatrixserverlib.Event
|
||||
event, err = buildEvent(&builder, &authEvents, cfg, evTime, gomatrixserverlib.RoomVersion(r.NewVersion))
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("buildEvent failed")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
||||
if err = gomatrixserverlib.Allowed(event, &authEvents); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.Allowed failed")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
||||
// Add the event to the list of auth events
|
||||
builtEvents = append(builtEvents, event.Headered(gomatrixserverlib.RoomVersion(r.NewVersion)))
|
||||
err = authEvents.AddEvent(event)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("authEvents.AddEvent failed")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
}
|
||||
|
||||
inputs := make([]roomserverAPI.InputRoomEvent, 0, len(builtEvents))
|
||||
for _, event := range builtEvents {
|
||||
inputs = append(inputs, roomserverAPI.InputRoomEvent{
|
||||
Kind: roomserverAPI.KindNew,
|
||||
Event: event,
|
||||
Origin: cfg.Matrix.ServerName,
|
||||
SendAsServer: roomserverAPI.DoNotSendToOtherServers,
|
||||
})
|
||||
}
|
||||
if err = roomserverAPI.SendInputRoomEvents(req.Context(), rsAPI, inputs, false); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("roomserverAPI.SendInputRoomEvents failed")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
||||
// check if the old room was published
|
||||
var pubQueryRes roomserverAPI.QueryPublishedRoomsResponse
|
||||
err = rsAPI.QueryPublishedRooms(req.Context(), &roomserverAPI.QueryPublishedRoomsRequest{
|
||||
RoomID: roomID,
|
||||
}, &pubQueryRes)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("QueryPublishedRooms failed")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
||||
// if the old room is published (was public), publish the new room
|
||||
if len(pubQueryRes.RoomIDs) == 1 {
|
||||
publishNewRoom(req, rsAPI, roomID, newRoomID)
|
||||
}
|
||||
|
||||
// Clear the old canonical alias event in the old room
|
||||
emptyCanonicalAliasEvent, resErr := makeHeaderedEvent(req, device, cfg, evTime, roomID, rsAPI, fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomCanonicalAlias,
|
||||
Content: map[string]interface{}{},
|
||||
})
|
||||
if resErr != nil {
|
||||
if resErr.Code == http.StatusForbidden {
|
||||
util.GetLogger(req.Context()).WithField(logrus.ErrorKey, resErr).Warn("UpgradeRoom: Could not set empty canonical alias event in old room")
|
||||
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 *resErr
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
} else {
|
||||
if resErr = sendHeaderedEvent(req, cfg, rsAPI, emptyCanonicalAliasEvent); resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Move local aliases to the new room
|
||||
if resErr = moveLocalAliases(req, roomID, newRoomID, userID, rsAPI); resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
|
||||
// 6. Restrict power levels in the old room
|
||||
if resErr = restrictOldRoomPowerLevels(req, device, cfg, evTime, roomID, rsAPI, *powerLevelContent); resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
Code: http.StatusOK,
|
||||
JSON: upgradeRoomResponse{
|
||||
ReplacementRoom: newRoomID,
|
||||
ReplacementRoom: upgradeResp.NewRoomID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func publishNewRoom(
|
||||
req *http.Request,
|
||||
rsAPI roomserverAPI.RoomserverInternalAPI,
|
||||
oldRoomID, newRoomID string,
|
||||
) {
|
||||
|
||||
// expose this room in the published room list
|
||||
var pubNewRoomRes roomserverAPI.PerformPublishResponse
|
||||
rsAPI.PerformPublish(req.Context(), &roomserverAPI.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(req.Context()).WithError(pubNewRoomRes.Error).Error("failed to visibility:public")
|
||||
}
|
||||
|
||||
var unpubOldRoomRes roomserverAPI.PerformPublishResponse
|
||||
// remove the old room from the published room list
|
||||
rsAPI.PerformPublish(req.Context(), &roomserverAPI.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(req.Context()).WithError(unpubOldRoomRes.Error).Error("failed to visibility:private")
|
||||
}
|
||||
}
|
||||
|
||||
func createTemporaryPowerLevels(powerLevelContent *gomatrixserverlib.PowerLevelContent, userID string) fledglingEvent {
|
||||
eventPowerLevels := powerLevelContent.Events
|
||||
stateDefaultPowerLevel := powerLevelContent.StateDefault
|
||||
neededPowerLevel := stateDefaultPowerLevel
|
||||
for _, powerLevel := range eventPowerLevels {
|
||||
if powerLevel > neededPowerLevel {
|
||||
neededPowerLevel = powerLevel
|
||||
}
|
||||
}
|
||||
|
||||
tempPowerLevelContent := &gomatrixserverlib.PowerLevelContent{}
|
||||
*tempPowerLevelContent = *powerLevelContent
|
||||
newUserPowerLevels := make(map[string]int64)
|
||||
for key, value := range powerLevelContent.Users {
|
||||
newUserPowerLevels[key] = value
|
||||
}
|
||||
tempPowerLevelContent.Users = newUserPowerLevels
|
||||
|
||||
if val, ok := tempPowerLevelContent.Users[userID]; ok {
|
||||
if val < neededPowerLevel {
|
||||
tempPowerLevelContent.Users[userID] = neededPowerLevel
|
||||
}
|
||||
} else {
|
||||
if tempPowerLevelContent.UsersDefault < val {
|
||||
tempPowerLevelContent.UsersDefault = neededPowerLevel
|
||||
}
|
||||
}
|
||||
tempPowerLevelsEvent := fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomPowerLevels,
|
||||
Content: tempPowerLevelContent,
|
||||
}
|
||||
return tempPowerLevelsEvent
|
||||
}
|
||||
|
||||
func getBanEvents(req *http.Request, roomID string, rsAPI roomserverAPI.RoomserverInternalAPI) ([]fledglingEvent, error) {
|
||||
var err error
|
||||
banEvents := []fledglingEvent{}
|
||||
|
||||
roomMemberReq := roomserverAPI.QueryCurrentStateRequest{RoomID: roomID, AllowWildcards: true, StateTuples: []gomatrixserverlib.StateKeyTuple{
|
||||
{EventType: gomatrixserverlib.MRoomMember, StateKey: "*"},
|
||||
}}
|
||||
roomMemberRes := roomserverAPI.QueryCurrentStateResponse{}
|
||||
if err = rsAPI.QueryCurrentState(req.Context(), &roomMemberReq, &roomMemberRes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, event := range roomMemberRes.StateEvents {
|
||||
if event == nil {
|
||||
continue
|
||||
}
|
||||
memberContent, err := gomatrixserverlib.NewMemberContentFromEvent(event.Event)
|
||||
if err != nil || memberContent.Membership != gomatrixserverlib.Ban {
|
||||
continue
|
||||
}
|
||||
banEvents = append(banEvents, fledglingEvent{Type: gomatrixserverlib.MRoomMember, StateKey: *event.StateKey(), Content: memberContent})
|
||||
}
|
||||
return banEvents, nil
|
||||
}
|
||||
|
||||
func moveLocalAliases(req *http.Request,
|
||||
roomID, newRoomID, userID string,
|
||||
rsAPI roomserverAPI.RoomserverInternalAPI) *util.JSONResponse {
|
||||
var err error
|
||||
internalServerError := jsonerror.InternalServerError()
|
||||
|
||||
aliasReq := roomserverAPI.GetAliasesForRoomIDRequest{RoomID: roomID}
|
||||
aliasRes := roomserverAPI.GetAliasesForRoomIDResponse{}
|
||||
if err = rsAPI.GetAliasesForRoomID(req.Context(), &aliasReq, &aliasRes); err != nil {
|
||||
return &internalServerError
|
||||
}
|
||||
|
||||
for _, alias := range aliasRes.Aliases {
|
||||
removeAliasReq := roomserverAPI.RemoveRoomAliasRequest{UserID: userID, Alias: alias}
|
||||
removeAliasRes := roomserverAPI.RemoveRoomAliasResponse{}
|
||||
if err = rsAPI.RemoveRoomAlias(req.Context(), &removeAliasReq, &removeAliasRes); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("roomserverAPI.RemoveRoomAlias failed")
|
||||
return &internalServerError
|
||||
}
|
||||
|
||||
setAliasReq := roomserverAPI.SetRoomAliasRequest{UserID: userID, Alias: alias, RoomID: newRoomID}
|
||||
setAliasRes := roomserverAPI.SetRoomAliasResponse{}
|
||||
if err = rsAPI.SetRoomAlias(req.Context(), &setAliasReq, &setAliasRes); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("roomserverAPI.SetRoomAlias failed")
|
||||
return &internalServerError
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func restrictOldRoomPowerLevels(req *http.Request, device *userapi.Device,
|
||||
cfg *config.ClientAPI, evTime time.Time,
|
||||
roomID string,
|
||||
rsAPI roomserverAPI.RoomserverInternalAPI, powerLevelContent gomatrixserverlib.PowerLevelContent) *util.JSONResponse {
|
||||
restrictedPowerLevelContent := &gomatrixserverlib.PowerLevelContent{}
|
||||
*restrictedPowerLevelContent = powerLevelContent
|
||||
|
||||
restrictedDefaultPowerLevel := int64(50)
|
||||
if restrictedPowerLevelContent.UsersDefault+1 > restrictedDefaultPowerLevel {
|
||||
restrictedDefaultPowerLevel = restrictedPowerLevelContent.UsersDefault + 1
|
||||
}
|
||||
restrictedPowerLevelContent.EventsDefault = restrictedDefaultPowerLevel
|
||||
restrictedPowerLevelContent.Invite = restrictedDefaultPowerLevel
|
||||
|
||||
restrictedPowerLevelsHeadered, resErr := makeHeaderedEvent(req, device, cfg, evTime, roomID, rsAPI, fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomPowerLevels,
|
||||
Content: restrictedPowerLevelContent,
|
||||
})
|
||||
if resErr != nil {
|
||||
if resErr.Code == http.StatusForbidden {
|
||||
util.GetLogger(req.Context()).WithField(logrus.ErrorKey, resErr).Warn("UpgradeRoom: Could not restrict power levels in old room")
|
||||
} else {
|
||||
return resErr
|
||||
}
|
||||
} else {
|
||||
if resErr = sendHeaderedEvent(req, cfg, rsAPI, restrictedPowerLevelsHeadered); resErr != nil {
|
||||
return resErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func userIsAuthorized(
|
||||
req *http.Request, device *userapi.Device,
|
||||
roomID string,
|
||||
rsAPI roomserverAPI.RoomserverInternalAPI,
|
||||
) *util.JSONResponse {
|
||||
plEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomPowerLevels,
|
||||
StateKey: "",
|
||||
})
|
||||
if plEvent == nil {
|
||||
return &util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("You don't have permission to upgrade this room, no power_levels event in this room."),
|
||||
}
|
||||
}
|
||||
|
||||
pl, err := plEvent.PowerLevels()
|
||||
if err != nil {
|
||||
return &util.JSONResponse{
|
||||
Code: 403,
|
||||
JSON: jsonerror.Forbidden("The power_levels event for this room is malformed so auth checks cannot be performed."),
|
||||
}
|
||||
}
|
||||
|
||||
// Check for power level required to send tombstone event (marks the curren room as obsolete),
|
||||
// if not found, use the StateDefault power level
|
||||
plToUpgrade, ok := pl.Events["m.room.tombstone"]
|
||||
if !ok {
|
||||
plToUpgrade = pl.StateDefault
|
||||
}
|
||||
|
||||
allowedToUpgrade := pl.UserLevel(device.UserID) >= plToUpgrade
|
||||
if !allowedToUpgrade {
|
||||
return &util.JSONResponse{
|
||||
Code: 403,
|
||||
JSON: jsonerror.Forbidden("You don't have permission to upgrade the room, power level too low."),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeHeaderedEvent(req *http.Request, device *userapi.Device,
|
||||
cfg *config.ClientAPI, evTime time.Time,
|
||||
roomID string,
|
||||
rsAPI roomserverAPI.RoomserverInternalAPI, event fledglingEvent) (*gomatrixserverlib.HeaderedEvent, *util.JSONResponse) {
|
||||
|
||||
builder := gomatrixserverlib.EventBuilder{
|
||||
Sender: device.UserID,
|
||||
RoomID: roomID,
|
||||
Type: event.Type,
|
||||
StateKey: &event.StateKey,
|
||||
}
|
||||
err := builder.SetContent(event.Content)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("builder.SetContent failed")
|
||||
resErr := jsonerror.InternalServerError()
|
||||
return nil, &resErr
|
||||
}
|
||||
var queryRes roomserverAPI.QueryLatestEventsAndStateResponse
|
||||
headeredEvent, err := eventutil.QueryAndBuildEvent(req.Context(), &builder, cfg.Matrix, evTime, rsAPI, &queryRes)
|
||||
if err == eventutil.ErrRoomNoExists {
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound("Room does not exist"),
|
||||
}
|
||||
} else if e, ok := err.(gomatrixserverlib.BadJSONError); ok {
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON(e.Error()),
|
||||
}
|
||||
} else if e, ok := err.(gomatrixserverlib.EventValidationError); ok {
|
||||
if e.Code == gomatrixserverlib.EventValidationTooLarge {
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusRequestEntityTooLarge,
|
||||
JSON: jsonerror.BadJSON(e.Error()),
|
||||
}
|
||||
}
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON(e.Error()),
|
||||
}
|
||||
} else if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("eventutil.BuildEvent failed")
|
||||
resErr := jsonerror.InternalServerError()
|
||||
return nil, &resErr
|
||||
}
|
||||
|
||||
// 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, &util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden(err.Error()), // TODO: Is this error string comprehensible to the client?
|
||||
}
|
||||
}
|
||||
|
||||
return headeredEvent, nil
|
||||
|
||||
}
|
||||
|
||||
func makeTombstoneEvent(
|
||||
req *http.Request, device *userapi.Device,
|
||||
cfg *config.ClientAPI, evTime time.Time,
|
||||
roomID, newRoomID string,
|
||||
rsAPI roomserverAPI.RoomserverInternalAPI,
|
||||
) (*gomatrixserverlib.HeaderedEvent, *util.JSONResponse) {
|
||||
content := map[string]interface{}{
|
||||
"body": "This room has been replaced",
|
||||
"replacement_room": newRoomID,
|
||||
}
|
||||
event := fledglingEvent{
|
||||
Type: "m.room.tombstone",
|
||||
Content: content,
|
||||
}
|
||||
return makeHeaderedEvent(req, device, cfg, evTime, roomID, rsAPI, event)
|
||||
|
||||
}
|
||||
|
||||
func sendHeaderedEvent(
|
||||
req *http.Request,
|
||||
cfg *config.ClientAPI,
|
||||
rsAPI roomserverAPI.RoomserverInternalAPI,
|
||||
headeredEvent *gomatrixserverlib.HeaderedEvent,
|
||||
) *util.JSONResponse {
|
||||
var inputs []roomserverAPI.InputRoomEvent
|
||||
inputs = append(inputs, roomserverAPI.InputRoomEvent{
|
||||
Kind: roomserverAPI.KindNew,
|
||||
Event: headeredEvent,
|
||||
Origin: cfg.Matrix.ServerName,
|
||||
SendAsServer: roomserverAPI.DoNotSendToOtherServers,
|
||||
})
|
||||
if err := roomserverAPI.SendInputRoomEvents(req.Context(), rsAPI, inputs, false); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("roomserverAPI.SendInputRoomEvents failed")
|
||||
resErr := jsonerror.InternalServerError()
|
||||
return &resErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshal(in []byte) map[string]interface{} {
|
||||
ret := make(map[string]interface{})
|
||||
err := json.Unmarshal(in, &ret)
|
||||
if err != nil {
|
||||
logrus.Fatalf("One of our own state events is not valid JSON: %v", err)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,6 +170,9 @@ type RoomserverInternalAPI interface {
|
|||
// PerformForget forgets a rooms history for a specific user
|
||||
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.
|
||||
QueryRoomVersionCapabilities(
|
||||
ctx context.Context,
|
||||
|
|
|
|||
|
|
@ -67,6 +67,15 @@ func (t *RoomserverInternalAPITrace) PerformUnpeek(
|
|||
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("PerformUnpeek req=%+v res=%+v", js(req), js(res))
|
||||
}
|
||||
|
||||
func (t *RoomserverInternalAPITrace) PerformJoin(
|
||||
ctx context.Context,
|
||||
req *PerformJoinRequest,
|
||||
|
|
|
|||
|
|
@ -203,3 +203,14 @@ type PerformForgetRequest 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.Backfiller
|
||||
*perform.Forgetter
|
||||
*perform.Upgrader
|
||||
ProcessContext *process.ProcessContext
|
||||
DB storage.Database
|
||||
Cfg *config.RoomServer
|
||||
|
|
@ -159,6 +160,10 @@ func (r *RoomserverInternalAPI) SetFederationAPI(fsAPI fsAPI.FederationInternalA
|
|||
r.Forgetter = &perform.Forgetter{
|
||||
DB: r.DB,
|
||||
}
|
||||
r.Upgrader = &perform.Upgrader{
|
||||
Cfg: r.Cfg,
|
||||
URSAPI: r,
|
||||
}
|
||||
|
||||
if err := r.Inputer.Start(); err != nil {
|
||||
logrus.WithError(err).Panic("failed to start roomserver input API")
|
||||
|
|
|
|||
795
roomserver/internal/perform/perform_upgrade.go
Normal file
795
roomserver/internal/perform/perform_upgrade.go
Normal file
|
|
@ -0,0 +1,795 @@
|
|||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// 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 = ""
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// 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, userID, roomID, newRoomID, 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, 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 {
|
||||
powerLevelContent, pErr := r.getRoomPowerLevels(ctx, roomID)
|
||||
if pErr != nil {
|
||||
return pErr
|
||||
}
|
||||
|
||||
restrictedPowerLevelContent := &gomatrixserverlib.PowerLevelContent{}
|
||||
*restrictedPowerLevelContent = *powerLevelContent
|
||||
|
||||
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,
|
||||
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, evTime time.Time, userID, roomID string) *api.PerformError {
|
||||
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 {
|
||||
publishNewRoom(ctx, r.URSAPI, roomID, newRoomID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func publishNewRoom(
|
||||
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 curren room as obsolete),
|
||||
// if not found, use the StateDefault power level
|
||||
plToUpgrade, ok := pl.Events["m.room.tombstone"]
|
||||
if !ok {
|
||||
plToUpgrade = pl.StateDefault
|
||||
}
|
||||
return pl.UserLevel(userID) >= plToUpgrade
|
||||
}
|
||||
|
||||
func (r *Upgrader) generateInitialEvents(ctx context.Context, userID, roomID, newRoomID, newVersion string, tombstoneEvent *gomatrixserverlib.HeaderedEvent) ([]fledglingEvent, *api.PerformError) {
|
||||
oldCreateEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomCreate,
|
||||
StateKey: "",
|
||||
})
|
||||
oldPowerLevelsEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomPowerLevels,
|
||||
StateKey: "",
|
||||
})
|
||||
oldJoinRulesEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomJoinRules,
|
||||
StateKey: "",
|
||||
})
|
||||
oldHistoryVisibilityEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomHistoryVisibility,
|
||||
StateKey: "",
|
||||
})
|
||||
oldNameEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomName,
|
||||
StateKey: "",
|
||||
})
|
||||
oldTopicEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomTopic,
|
||||
StateKey: "",
|
||||
})
|
||||
oldGuestAccessEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomGuestAccess,
|
||||
StateKey: "",
|
||||
})
|
||||
oldAvatarEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomAvatar,
|
||||
StateKey: "",
|
||||
})
|
||||
oldEncryptionEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomEncryption,
|
||||
StateKey: "",
|
||||
})
|
||||
oldServerAclEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: "m.room.server_acl",
|
||||
StateKey: "",
|
||||
})
|
||||
// Not in the spec, but needed for sytest compatablility
|
||||
oldRelatedGroupsEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: "m.room.related_groups",
|
||||
StateKey: "",
|
||||
})
|
||||
oldCanonicalAliasEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomCanonicalAlias,
|
||||
StateKey: "",
|
||||
})
|
||||
|
||||
newCreateContent := map[string]interface{}{
|
||||
"creator": userID,
|
||||
"room_version": newVersion, // TODO: change struct to single var?
|
||||
"predecessor": gomatrixserverlib.PreviousRoom{
|
||||
EventID: tombstoneEvent.EventID(),
|
||||
RoomID: roomID,
|
||||
},
|
||||
}
|
||||
oldCreateContent := unmarshal(oldCreateEvent.Content())
|
||||
if federate, ok := oldCreateContent["m.federate"].(bool); ok {
|
||||
newCreateContent["m.federate"] = federate
|
||||
}
|
||||
|
||||
newCreateEvent := fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomCreate,
|
||||
Content: newCreateContent,
|
||||
}
|
||||
|
||||
membershipEvent := fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomMember,
|
||||
StateKey: userID,
|
||||
Content: gomatrixserverlib.MemberContent{
|
||||
Membership: gomatrixserverlib.Join,
|
||||
DisplayName: "",
|
||||
AvatarURL: "", // TODO
|
||||
},
|
||||
}
|
||||
|
||||
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",
|
||||
}
|
||||
}
|
||||
|
||||
newPowerLevelsEvent := fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomPowerLevels,
|
||||
Content: powerLevelContent,
|
||||
}
|
||||
|
||||
//create temporary power level event that elevates upgrading user's prvileges to create every copied state event
|
||||
tempPowerLevelsEvent := createTemporaryPowerLevels(powerLevelContent, userID)
|
||||
|
||||
joinRulesContent, err := oldJoinRulesEvent.JoinRule()
|
||||
if err != nil {
|
||||
return nil, &api.PerformError{
|
||||
Msg: "Join rules event had bad content",
|
||||
}
|
||||
}
|
||||
newJoinRulesEvent := fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomJoinRules,
|
||||
Content: map[string]interface{}{
|
||||
"join_rule": joinRulesContent,
|
||||
},
|
||||
}
|
||||
|
||||
historyVisibilityContent, err := oldHistoryVisibilityEvent.HistoryVisibility()
|
||||
if err != nil {
|
||||
return nil, &api.PerformError{
|
||||
Msg: "History visibility event had bad content",
|
||||
}
|
||||
}
|
||||
newHistoryVisibilityEvent := fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomHistoryVisibility,
|
||||
Content: map[string]interface{}{
|
||||
"history_visibility": historyVisibilityContent,
|
||||
},
|
||||
}
|
||||
|
||||
var newNameEvent fledglingEvent
|
||||
var newTopicEvent fledglingEvent
|
||||
var newGuestAccessEvent fledglingEvent
|
||||
var newAvatarEvent fledglingEvent
|
||||
var newEncryptionEvent fledglingEvent
|
||||
var newServerACLEvent fledglingEvent
|
||||
var newRelatedGroupsEvent fledglingEvent
|
||||
var newCanonicalAliasEvent fledglingEvent
|
||||
|
||||
if oldNameEvent != nil {
|
||||
newNameEvent = fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomName,
|
||||
Content: unmarshal(oldNameEvent.Content()),
|
||||
}
|
||||
}
|
||||
if oldTopicEvent != nil {
|
||||
newTopicEvent = fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomTopic,
|
||||
Content: unmarshal(oldTopicEvent.Content()),
|
||||
}
|
||||
}
|
||||
if oldGuestAccessEvent != nil {
|
||||
newGuestAccessEvent = fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomGuestAccess,
|
||||
Content: unmarshal(oldGuestAccessEvent.Content()),
|
||||
}
|
||||
}
|
||||
if oldAvatarEvent != nil {
|
||||
newAvatarEvent = fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomAvatar,
|
||||
Content: unmarshal(oldAvatarEvent.Content()),
|
||||
}
|
||||
}
|
||||
if oldEncryptionEvent != nil {
|
||||
newEncryptionEvent = fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomEncryption,
|
||||
Content: unmarshal(oldEncryptionEvent.Content()),
|
||||
}
|
||||
}
|
||||
if oldServerAclEvent != nil {
|
||||
newServerACLEvent = fledglingEvent{
|
||||
Type: "m.room.server_acl",
|
||||
Content: unmarshal(oldServerAclEvent.Content()),
|
||||
}
|
||||
}
|
||||
if oldRelatedGroupsEvent != nil {
|
||||
newRelatedGroupsEvent = fledglingEvent{
|
||||
Type: "m.room.related_groups",
|
||||
Content: unmarshal(oldRelatedGroupsEvent.Content()),
|
||||
}
|
||||
}
|
||||
if oldCanonicalAliasEvent != nil {
|
||||
newCanonicalAliasEvent = fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomCanonicalAlias,
|
||||
Content: unmarshal(oldCanonicalAliasEvent.Content()),
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Replicate transferable state events
|
||||
// send events into the room in order of:
|
||||
// 1- m.room.create
|
||||
// 2- m.room.power_levels (temporary, to allow the upgrading user to send everything)
|
||||
// 3- m.room.join_rules
|
||||
// 4- m.room.history_visibility
|
||||
// 5- m.room.guest_access
|
||||
// 6- m.room.name
|
||||
// 7- m.room.avatar
|
||||
// 8- m.room.topic
|
||||
// 9- m.room.encryption
|
||||
// 10-m.room.server_acl
|
||||
// 11-m.room.related_groups
|
||||
// 12-m.room.canonical_alias
|
||||
// 13-All ban events from the old room
|
||||
// 14-The original room power levels
|
||||
eventsToMake := []fledglingEvent{
|
||||
newCreateEvent, membershipEvent, tempPowerLevelsEvent, newJoinRulesEvent, newHistoryVisibilityEvent,
|
||||
}
|
||||
if oldGuestAccessEvent != nil {
|
||||
eventsToMake = append(eventsToMake, newGuestAccessEvent)
|
||||
} else { // Always create this with the default value to appease sytests
|
||||
eventsToMake = append(eventsToMake, fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomGuestAccess,
|
||||
Content: map[string]interface{}{"guest_access": "forbidden"},
|
||||
})
|
||||
}
|
||||
if oldNameEvent != nil {
|
||||
eventsToMake = append(eventsToMake, newNameEvent)
|
||||
}
|
||||
if oldAvatarEvent != nil {
|
||||
eventsToMake = append(eventsToMake, newAvatarEvent)
|
||||
}
|
||||
if oldTopicEvent != nil {
|
||||
eventsToMake = append(eventsToMake, newTopicEvent)
|
||||
}
|
||||
if oldEncryptionEvent != nil {
|
||||
eventsToMake = append(eventsToMake, newEncryptionEvent)
|
||||
}
|
||||
if oldServerAclEvent != nil {
|
||||
eventsToMake = append(eventsToMake, newServerACLEvent)
|
||||
}
|
||||
if oldRelatedGroupsEvent != nil {
|
||||
eventsToMake = append(eventsToMake, newRelatedGroupsEvent)
|
||||
}
|
||||
if oldCanonicalAliasEvent != nil {
|
||||
eventsToMake = append(eventsToMake, newCanonicalAliasEvent)
|
||||
}
|
||||
banEvents, err := getBanEvents(ctx, roomID, r.URSAPI)
|
||||
if err != nil {
|
||||
return nil, &api.PerformError{
|
||||
Msg: err.Error(),
|
||||
}
|
||||
} else {
|
||||
eventsToMake = append(eventsToMake, banEvents...)
|
||||
}
|
||||
eventsToMake = append(eventsToMake, newPowerLevelsEvent)
|
||||
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 getBanEvents(ctx context.Context, roomID string, URSAPI api.RoomserverInternalAPI) ([]fledglingEvent, error) {
|
||||
var err error
|
||||
banEvents := []fledglingEvent{}
|
||||
|
||||
roomMemberReq := api.QueryCurrentStateRequest{RoomID: roomID, AllowWildcards: true, StateTuples: []gomatrixserverlib.StateKeyTuple{
|
||||
{EventType: gomatrixserverlib.MRoomMember, StateKey: "*"},
|
||||
}}
|
||||
roomMemberRes := api.QueryCurrentStateResponse{}
|
||||
if err = URSAPI.QueryCurrentState(ctx, &roomMemberReq, &roomMemberRes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, event := range roomMemberRes.StateEvents {
|
||||
if event == nil {
|
||||
continue
|
||||
}
|
||||
memberContent, err := gomatrixserverlib.NewMemberContentFromEvent(event.Event)
|
||||
if err != nil || memberContent.Membership != gomatrixserverlib.Ban {
|
||||
continue
|
||||
}
|
||||
banEvents = append(banEvents, fledglingEvent{Type: gomatrixserverlib.MRoomMember, StateKey: *event.StateKey(), Content: memberContent})
|
||||
}
|
||||
return banEvents, nil
|
||||
}
|
||||
|
||||
func createTemporaryPowerLevels(powerLevelContent *gomatrixserverlib.PowerLevelContent, userID string) fledglingEvent {
|
||||
eventPowerLevels := powerLevelContent.Events
|
||||
stateDefaultPowerLevel := powerLevelContent.StateDefault
|
||||
neededPowerLevel := stateDefaultPowerLevel
|
||||
for _, powerLevel := range eventPowerLevels {
|
||||
if powerLevel > neededPowerLevel {
|
||||
neededPowerLevel = powerLevel
|
||||
}
|
||||
}
|
||||
|
||||
tempPowerLevelContent := &gomatrixserverlib.PowerLevelContent{}
|
||||
*tempPowerLevelContent = *powerLevelContent
|
||||
newUserPowerLevels := make(map[string]int64)
|
||||
for key, value := range powerLevelContent.Users {
|
||||
newUserPowerLevels[key] = value
|
||||
}
|
||||
tempPowerLevelContent.Users = newUserPowerLevels
|
||||
|
||||
if val, ok := tempPowerLevelContent.Users[userID]; ok {
|
||||
if val < neededPowerLevel {
|
||||
tempPowerLevelContent.Users[userID] = neededPowerLevel
|
||||
}
|
||||
} else {
|
||||
if tempPowerLevelContent.UsersDefault < val {
|
||||
tempPowerLevelContent.UsersDefault = neededPowerLevel
|
||||
}
|
||||
}
|
||||
tempPowerLevelsEvent := fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomPowerLevels,
|
||||
Content: tempPowerLevelContent,
|
||||
}
|
||||
return tempPowerLevelsEvent
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func unmarshal(in []byte) map[string]interface{} {
|
||||
ret := make(map[string]interface{})
|
||||
err := json.Unmarshal(in, &ret)
|
||||
if err != nil {
|
||||
logrus.Fatalf("One of our own state events is not valid JSON: %v", err)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@ const (
|
|||
RoomserverPerformInvitePath = "/roomserver/performInvite"
|
||||
RoomserverPerformPeekPath = "/roomserver/performPeek"
|
||||
RoomserverPerformUnpeekPath = "/roomserver/performUnpeek"
|
||||
RoomserverPerformRoomUpgradePath = "/roomserver/performRoomUpgrade"
|
||||
RoomserverPerformJoinPath = "/roomserver/performJoin"
|
||||
RoomserverPerformLeavePath = "/roomserver/performLeave"
|
||||
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(
|
||||
ctx context.Context,
|
||||
request *api.PerformLeaveRequest,
|
||||
|
|
|
|||
|
|
@ -96,6 +96,17 @@ func AddRoutes(r api.RoomserverInternalAPI, internalAPIMux *mux.Router) {
|
|||
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
||||
}),
|
||||
)
|
||||
internalAPIMux.Handle(RoomserverPerformPeekPath,
|
||||
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,
|
||||
httputil.MakeInternalAPI("performPublish", func(req *http.Request) util.JSONResponse {
|
||||
var request api.PerformPublishRequest
|
||||
|
|
|
|||
Loading…
Reference in a new issue