mirror of
https://github.com/matrix-org/dendrite.git
synced 2026-01-10 15:43:09 -06:00
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/roomversions
This commit is contained in:
commit
735c35aff1
|
|
@ -22,17 +22,13 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/roomserver/types"
|
||||
roomserverVersion "github.com/matrix-org/dendrite/roomserver/version"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
|
|
@ -41,32 +37,19 @@ import (
|
|||
|
||||
// https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
|
||||
type createRoomRequest struct {
|
||||
Invite []string `json:"invite"`
|
||||
Name string `json:"name"`
|
||||
Visibility string `json:"visibility"`
|
||||
Topic string `json:"topic"`
|
||||
Preset string `json:"preset"`
|
||||
CreationContent json.RawMessage `json:"creation_content"`
|
||||
InitialState []fledglingEvent `json:"initial_state"`
|
||||
RoomAliasName string `json:"room_alias_name"`
|
||||
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
|
||||
PowerLevelContentOverride json.RawMessage `json:"power_level_content_override"`
|
||||
IsDirect bool `json:"is_direct"`
|
||||
Invite []string `json:"invite"`
|
||||
Name string `json:"name"`
|
||||
Visibility string `json:"visibility"`
|
||||
Topic string `json:"topic"`
|
||||
Preset string `json:"preset"`
|
||||
CreationContent json.RawMessage `json:"creation_content"`
|
||||
InitialState []gomatrixserverlib.FledglingEvent `json:"initial_state"`
|
||||
RoomAliasName string `json:"room_alias_name"`
|
||||
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
|
||||
PowerLevelContentOverride json.RawMessage `json:"power_level_content_override"`
|
||||
IsDirect bool `json:"is_direct"`
|
||||
}
|
||||
|
||||
const (
|
||||
presetPrivateChat = "private_chat"
|
||||
presetTrustedPrivateChat = "trusted_private_chat"
|
||||
presetPublicChat = "public_chat"
|
||||
)
|
||||
|
||||
const (
|
||||
historyVisibilityShared = "shared"
|
||||
// TODO: These should be implemented once history visibility is implemented
|
||||
// historyVisibilityWorldReadable = "world_readable"
|
||||
// historyVisibilityInvited = "invited"
|
||||
)
|
||||
|
||||
func (r createRoomRequest) Validate() *util.JSONResponse {
|
||||
whitespace := "\t\n\x0b\x0c\r " // https://docs.python.org/2/library/string.html#string.whitespace
|
||||
// https://github.com/matrix-org/synapse/blob/v0.19.2/synapse/handlers/room.py#L81
|
||||
|
|
@ -78,12 +61,7 @@ func (r createRoomRequest) Validate() *util.JSONResponse {
|
|||
}
|
||||
}
|
||||
for _, userID := range r.Invite {
|
||||
// TODO: We should put user ID parsing code into gomatrixserverlib and use that instead
|
||||
// (see https://github.com/matrix-org/gomatrixserverlib/blob/3394e7c7003312043208aa73727d2256eea3d1f6/eventcontent.go#L347 )
|
||||
// It should be a struct (with pointers into a single string to avoid copying) and
|
||||
// we should update all refs to use UserID types rather than strings.
|
||||
// https://github.com/matrix-org/synapse/blob/v0.19.2/synapse/types.py#L92
|
||||
if _, _, err := gomatrixserverlib.SplitID('@', userID); err != nil {
|
||||
if _, err := spec.NewUserID(userID, true); err != nil {
|
||||
return &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.BadJSON("user id must be in the form @localpart:domain"),
|
||||
|
|
@ -91,7 +69,7 @@ func (r createRoomRequest) Validate() *util.JSONResponse {
|
|||
}
|
||||
}
|
||||
switch r.Preset {
|
||||
case presetPrivateChat, presetTrustedPrivateChat, presetPublicChat, "":
|
||||
case spec.PresetPrivateChat, spec.PresetTrustedPrivateChat, spec.PresetPublicChat, "":
|
||||
default:
|
||||
return &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -129,13 +107,6 @@ type createRoomResponse struct {
|
|||
RoomAlias string `json:"room_alias,omitempty"` // in synapse not spec
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// CreateRoom implements /createRoom
|
||||
func CreateRoom(
|
||||
req *http.Request, device *api.Device,
|
||||
|
|
@ -143,12 +114,12 @@ func CreateRoom(
|
|||
profileAPI api.ClientUserAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||
asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
) util.JSONResponse {
|
||||
var r createRoomRequest
|
||||
resErr := httputil.UnmarshalJSONRequest(req, &r)
|
||||
var createRequest createRoomRequest
|
||||
resErr := httputil.UnmarshalJSONRequest(req, &createRequest)
|
||||
if resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
if resErr = r.Validate(); resErr != nil {
|
||||
if resErr = createRequest.Validate(); resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
evTime, err := httputil.ParseTSParam(req)
|
||||
|
|
@ -158,46 +129,51 @@ func CreateRoom(
|
|||
JSON: spec.InvalidParam(err.Error()),
|
||||
}
|
||||
}
|
||||
return createRoom(req.Context(), r, device, cfg, profileAPI, rsAPI, asAPI, evTime)
|
||||
return createRoom(req.Context(), createRequest, device, cfg, profileAPI, rsAPI, asAPI, evTime)
|
||||
}
|
||||
|
||||
// createRoom implements /createRoom
|
||||
// nolint: gocyclo
|
||||
func createRoom(
|
||||
ctx context.Context,
|
||||
r createRoomRequest, device *api.Device,
|
||||
createRequest createRoomRequest, device *api.Device,
|
||||
cfg *config.ClientAPI,
|
||||
profileAPI api.ClientUserAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||
asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
evTime time.Time,
|
||||
) util.JSONResponse {
|
||||
_, userDomain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||
userID, err := spec.NewUserID(device.UserID, true)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||
util.GetLogger(ctx).WithError(err).Error("invalid userID")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
if !cfg.Matrix.IsLocalServerName(userDomain) {
|
||||
if !cfg.Matrix.IsLocalServerName(userID.Domain()) {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: spec.Forbidden(fmt.Sprintf("User domain %q not configured locally", userDomain)),
|
||||
JSON: spec.Forbidden(fmt.Sprintf("User domain %q not configured locally", userID.Domain())),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (#267): Check room ID doesn't clash with an existing one, and we
|
||||
// probably shouldn't be using pseudo-random strings, maybe GUIDs?
|
||||
roomID := fmt.Sprintf("!%s:%s", util.RandomString(16), userDomain)
|
||||
|
||||
logger := util.GetLogger(ctx)
|
||||
userID := device.UserID
|
||||
|
||||
// TODO: Check room ID doesn't clash with an existing one, and we
|
||||
// probably shouldn't be using pseudo-random strings, maybe GUIDs?
|
||||
roomID, err := spec.NewRoomID(fmt.Sprintf("!%s:%s", util.RandomString(16), userID.Domain()))
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("invalid roomID")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
// Clobber keys: creator, room_version
|
||||
|
||||
roomVersion := roomserverVersion.DefaultRoomVersion()
|
||||
if r.RoomVersion != "" {
|
||||
candidateVersion := gomatrixserverlib.RoomVersion(r.RoomVersion)
|
||||
if createRequest.RoomVersion != "" {
|
||||
candidateVersion := gomatrixserverlib.RoomVersion(createRequest.RoomVersion)
|
||||
_, roomVersionError := roomserverVersion.SupportedRoomVersion(candidateVersion)
|
||||
if roomVersionError != nil {
|
||||
return util.JSONResponse{
|
||||
|
|
@ -208,17 +184,13 @@ func createRoom(
|
|||
roomVersion = candidateVersion
|
||||
}
|
||||
|
||||
// TODO: visibility/presets/raw initial state
|
||||
// TODO: Create room alias association
|
||||
// Make sure this doesn't fall into an application service's namespace though!
|
||||
|
||||
logger.WithFields(log.Fields{
|
||||
"userID": userID,
|
||||
"roomID": roomID,
|
||||
"userID": userID.String(),
|
||||
"roomID": roomID.String(),
|
||||
"roomVersion": roomVersion,
|
||||
}).Info("Creating new room")
|
||||
|
||||
profile, err := appserviceAPI.RetrieveUserProfile(ctx, userID, asAPI, profileAPI)
|
||||
profile, err := appserviceAPI.RetrieveUserProfile(ctx, userID.String(), asAPI, profileAPI)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("appserviceAPI.RetrieveUserProfile failed")
|
||||
return util.JSONResponse{
|
||||
|
|
@ -227,427 +199,38 @@ func createRoom(
|
|||
}
|
||||
}
|
||||
|
||||
createContent := map[string]interface{}{}
|
||||
if len(r.CreationContent) > 0 {
|
||||
if err = json.Unmarshal(r.CreationContent, &createContent); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for creation_content failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.BadJSON("invalid create content"),
|
||||
}
|
||||
}
|
||||
userDisplayName := profile.DisplayName
|
||||
userAvatarURL := profile.AvatarURL
|
||||
|
||||
keyID := cfg.Matrix.KeyID
|
||||
privateKey := cfg.Matrix.PrivateKey
|
||||
|
||||
req := roomserverAPI.PerformCreateRoomRequest{
|
||||
InvitedUsers: createRequest.Invite,
|
||||
RoomName: createRequest.Name,
|
||||
Visibility: createRequest.Visibility,
|
||||
Topic: createRequest.Topic,
|
||||
StatePreset: createRequest.Preset,
|
||||
CreationContent: createRequest.CreationContent,
|
||||
InitialState: createRequest.InitialState,
|
||||
RoomAliasName: createRequest.RoomAliasName,
|
||||
RoomVersion: roomVersion,
|
||||
PowerLevelContentOverride: createRequest.PowerLevelContentOverride,
|
||||
IsDirect: createRequest.IsDirect,
|
||||
|
||||
UserDisplayName: userDisplayName,
|
||||
UserAvatarURL: userAvatarURL,
|
||||
KeyID: keyID,
|
||||
PrivateKey: privateKey,
|
||||
EventTime: evTime,
|
||||
}
|
||||
createContent["creator"] = userID
|
||||
createContent["room_version"] = roomVersion
|
||||
powerLevelContent := eventutil.InitialPowerLevelsContent(userID)
|
||||
joinRuleContent := gomatrixserverlib.JoinRuleContent{
|
||||
JoinRule: spec.Invite,
|
||||
}
|
||||
historyVisibilityContent := gomatrixserverlib.HistoryVisibilityContent{
|
||||
HistoryVisibility: historyVisibilityShared,
|
||||
}
|
||||
|
||||
if r.PowerLevelContentOverride != nil {
|
||||
// Merge powerLevelContentOverride fields by unmarshalling it atop the defaults
|
||||
err = json.Unmarshal(r.PowerLevelContentOverride, &powerLevelContent)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for power_level_content_override failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.BadJSON("malformed power_level_content_override"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var guestsCanJoin bool
|
||||
switch r.Preset {
|
||||
case presetPrivateChat:
|
||||
joinRuleContent.JoinRule = spec.Invite
|
||||
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
|
||||
guestsCanJoin = true
|
||||
case presetTrustedPrivateChat:
|
||||
joinRuleContent.JoinRule = spec.Invite
|
||||
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
|
||||
for _, invitee := range r.Invite {
|
||||
powerLevelContent.Users[invitee] = 100
|
||||
}
|
||||
guestsCanJoin = true
|
||||
case presetPublicChat:
|
||||
joinRuleContent.JoinRule = spec.Public
|
||||
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
|
||||
}
|
||||
|
||||
createEvent := fledglingEvent{
|
||||
Type: spec.MRoomCreate,
|
||||
Content: createContent,
|
||||
}
|
||||
powerLevelEvent := fledglingEvent{
|
||||
Type: spec.MRoomPowerLevels,
|
||||
Content: powerLevelContent,
|
||||
}
|
||||
joinRuleEvent := fledglingEvent{
|
||||
Type: spec.MRoomJoinRules,
|
||||
Content: joinRuleContent,
|
||||
}
|
||||
historyVisibilityEvent := fledglingEvent{
|
||||
Type: spec.MRoomHistoryVisibility,
|
||||
Content: historyVisibilityContent,
|
||||
}
|
||||
membershipEvent := fledglingEvent{
|
||||
Type: spec.MRoomMember,
|
||||
StateKey: userID,
|
||||
Content: gomatrixserverlib.MemberContent{
|
||||
Membership: spec.Join,
|
||||
DisplayName: profile.DisplayName,
|
||||
AvatarURL: profile.AvatarURL,
|
||||
},
|
||||
}
|
||||
|
||||
var nameEvent *fledglingEvent
|
||||
var topicEvent *fledglingEvent
|
||||
var guestAccessEvent *fledglingEvent
|
||||
var aliasEvent *fledglingEvent
|
||||
|
||||
if r.Name != "" {
|
||||
nameEvent = &fledglingEvent{
|
||||
Type: spec.MRoomName,
|
||||
Content: eventutil.NameContent{
|
||||
Name: r.Name,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if r.Topic != "" {
|
||||
topicEvent = &fledglingEvent{
|
||||
Type: spec.MRoomTopic,
|
||||
Content: eventutil.TopicContent{
|
||||
Topic: r.Topic,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if guestsCanJoin {
|
||||
guestAccessEvent = &fledglingEvent{
|
||||
Type: spec.MRoomGuestAccess,
|
||||
Content: eventutil.GuestAccessContent{
|
||||
GuestAccess: "can_join",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var roomAlias string
|
||||
if r.RoomAliasName != "" {
|
||||
roomAlias = fmt.Sprintf("#%s:%s", r.RoomAliasName, userDomain)
|
||||
// check it's free TODO: This races but is better than nothing
|
||||
hasAliasReq := roomserverAPI.GetRoomIDForAliasRequest{
|
||||
Alias: roomAlias,
|
||||
IncludeAppservices: false,
|
||||
}
|
||||
|
||||
var aliasResp roomserverAPI.GetRoomIDForAliasResponse
|
||||
err = rsAPI.GetRoomIDForAlias(ctx, &hasAliasReq, &aliasResp)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("aliasAPI.GetRoomIDForAlias failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
if aliasResp.RoomID != "" {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.RoomInUse("Room ID already exists."),
|
||||
}
|
||||
}
|
||||
|
||||
aliasEvent = &fledglingEvent{
|
||||
Type: spec.MRoomCanonicalAlias,
|
||||
Content: eventutil.CanonicalAlias{
|
||||
Alias: roomAlias,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var initialStateEvents []fledglingEvent
|
||||
for i := range r.InitialState {
|
||||
if r.InitialState[i].StateKey != "" {
|
||||
initialStateEvents = append(initialStateEvents, r.InitialState[i])
|
||||
continue
|
||||
}
|
||||
|
||||
switch r.InitialState[i].Type {
|
||||
case spec.MRoomCreate:
|
||||
continue
|
||||
|
||||
case spec.MRoomPowerLevels:
|
||||
powerLevelEvent = r.InitialState[i]
|
||||
|
||||
case spec.MRoomJoinRules:
|
||||
joinRuleEvent = r.InitialState[i]
|
||||
|
||||
case spec.MRoomHistoryVisibility:
|
||||
historyVisibilityEvent = r.InitialState[i]
|
||||
|
||||
case spec.MRoomGuestAccess:
|
||||
guestAccessEvent = &r.InitialState[i]
|
||||
|
||||
case spec.MRoomName:
|
||||
nameEvent = &r.InitialState[i]
|
||||
|
||||
case spec.MRoomTopic:
|
||||
topicEvent = &r.InitialState[i]
|
||||
|
||||
default:
|
||||
initialStateEvents = append(initialStateEvents, r.InitialState[i])
|
||||
}
|
||||
}
|
||||
|
||||
// send events into the room in order of:
|
||||
// 1- m.room.create
|
||||
// 2- room creator join member
|
||||
// 3- m.room.power_levels
|
||||
// 4- m.room.join_rules
|
||||
// 5- m.room.history_visibility
|
||||
// 6- m.room.canonical_alias (opt)
|
||||
// 7- m.room.guest_access (opt)
|
||||
// 8- other initial state items
|
||||
// 9- m.room.name (opt)
|
||||
// 10- m.room.topic (opt)
|
||||
// 11- invite events (opt) - with is_direct flag if applicable TODO
|
||||
// 12- 3pid invite events (opt) TODO
|
||||
// This differs from Synapse slightly. Synapse would vary the ordering of 3-7
|
||||
// depending on if those events were in "initial_state" or not. This made it
|
||||
// harder to reason about, hence sticking to a strict static ordering.
|
||||
// TODO: Synapse has txn/token ID on each event. Do we need to do this here?
|
||||
eventsToMake := []fledglingEvent{
|
||||
createEvent, membershipEvent, powerLevelEvent, joinRuleEvent, historyVisibilityEvent,
|
||||
}
|
||||
if guestAccessEvent != nil {
|
||||
eventsToMake = append(eventsToMake, *guestAccessEvent)
|
||||
}
|
||||
eventsToMake = append(eventsToMake, initialStateEvents...)
|
||||
if nameEvent != nil {
|
||||
eventsToMake = append(eventsToMake, *nameEvent)
|
||||
}
|
||||
if topicEvent != nil {
|
||||
eventsToMake = append(eventsToMake, *topicEvent)
|
||||
}
|
||||
if aliasEvent != nil {
|
||||
// TODO: bit of a chicken and egg problem here as the alias doesn't exist and cannot until we have made the room.
|
||||
// This means we might fail creating the alias but say the canonical alias is something that doesn't exist.
|
||||
eventsToMake = append(eventsToMake, *aliasEvent)
|
||||
}
|
||||
|
||||
// TODO: invite events
|
||||
// TODO: 3pid invite events
|
||||
|
||||
verImpl, err := gomatrixserverlib.GetRoomVersion(roomVersion)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.BadJSON("unknown room version"),
|
||||
}
|
||||
}
|
||||
|
||||
var builtEvents []*types.HeaderedEvent
|
||||
authEvents := gomatrixserverlib.NewAuthEvents(nil)
|
||||
for i, e := range eventsToMake {
|
||||
depth := i + 1 // depth starts at 1
|
||||
|
||||
builder := verImpl.NewEventBuilderFromProtoEvent(&gomatrixserverlib.ProtoEvent{
|
||||
Sender: userID,
|
||||
RoomID: roomID,
|
||||
Type: e.Type,
|
||||
StateKey: &e.StateKey,
|
||||
Depth: int64(depth),
|
||||
})
|
||||
err = builder.SetContent(e.Content)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("builder.SetContent failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
if i > 0 {
|
||||
builder.PrevEvents = []string{builtEvents[i-1].EventID()}
|
||||
}
|
||||
var ev gomatrixserverlib.PDU
|
||||
if err = builder.AddAuthEvents(&authEvents); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("AddAuthEvents failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
ev, err = builder.Build(evTime, userDomain, cfg.Matrix.KeyID, cfg.Matrix.PrivateKey)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("buildEvent failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
if err = gomatrixserverlib.Allowed(ev, &authEvents); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.Allowed failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
// Add the event to the list of auth events
|
||||
builtEvents = append(builtEvents, &types.HeaderedEvent{PDU: ev})
|
||||
err = authEvents.AddEvent(ev)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("authEvents.AddEvent failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputs := make([]roomserverAPI.InputRoomEvent, 0, len(builtEvents))
|
||||
for _, event := range builtEvents {
|
||||
inputs = append(inputs, roomserverAPI.InputRoomEvent{
|
||||
Kind: roomserverAPI.KindNew,
|
||||
Event: event,
|
||||
Origin: userDomain,
|
||||
SendAsServer: roomserverAPI.DoNotSendToOtherServers,
|
||||
})
|
||||
}
|
||||
if err = roomserverAPI.SendInputRoomEvents(ctx, rsAPI, device.UserDomain(), inputs, false); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("roomserverAPI.SendInputRoomEvents failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(#269): Reserve room alias while we create the room. This stops us
|
||||
// from creating the room but still failing due to the alias having already
|
||||
// been taken.
|
||||
if roomAlias != "" {
|
||||
aliasReq := roomserverAPI.SetRoomAliasRequest{
|
||||
Alias: roomAlias,
|
||||
RoomID: roomID,
|
||||
UserID: userID,
|
||||
}
|
||||
|
||||
var aliasResp roomserverAPI.SetRoomAliasResponse
|
||||
err = rsAPI.SetRoomAlias(ctx, &aliasReq, &aliasResp)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("aliasAPI.SetRoomAlias failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
if aliasResp.AliasExists {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.RoomInUse("Room alias already exists."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a direct message then we should invite the participants.
|
||||
if len(r.Invite) > 0 {
|
||||
// Build some stripped state for the invite.
|
||||
var globalStrippedState []fclient.InviteV2StrippedState
|
||||
for _, event := range builtEvents {
|
||||
// Chosen events from the spec:
|
||||
// https://spec.matrix.org/v1.3/client-server-api/#stripped-state
|
||||
switch event.Type() {
|
||||
case spec.MRoomCreate:
|
||||
fallthrough
|
||||
case spec.MRoomName:
|
||||
fallthrough
|
||||
case spec.MRoomAvatar:
|
||||
fallthrough
|
||||
case spec.MRoomTopic:
|
||||
fallthrough
|
||||
case spec.MRoomCanonicalAlias:
|
||||
fallthrough
|
||||
case spec.MRoomEncryption:
|
||||
fallthrough
|
||||
case spec.MRoomMember:
|
||||
fallthrough
|
||||
case spec.MRoomJoinRules:
|
||||
ev := event.PDU
|
||||
globalStrippedState = append(
|
||||
globalStrippedState,
|
||||
fclient.NewInviteV2StrippedState(ev),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Process the invites.
|
||||
var inviteEvent *types.HeaderedEvent
|
||||
for _, invitee := range r.Invite {
|
||||
// Build the invite event.
|
||||
inviteEvent, err = buildMembershipEvent(
|
||||
ctx, invitee, "", profileAPI, device, spec.Invite,
|
||||
roomID, r.IsDirect, cfg, evTime, rsAPI, asAPI,
|
||||
)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("buildMembershipEvent failed")
|
||||
continue
|
||||
}
|
||||
inviteStrippedState := append(
|
||||
globalStrippedState,
|
||||
fclient.NewInviteV2StrippedState(inviteEvent.PDU),
|
||||
)
|
||||
// Send the invite event to the roomserver.
|
||||
event := inviteEvent
|
||||
err = rsAPI.PerformInvite(ctx, &roomserverAPI.PerformInviteRequest{
|
||||
Event: event,
|
||||
InviteRoomState: inviteStrippedState,
|
||||
RoomVersion: event.Version(),
|
||||
SendAsServer: string(userDomain),
|
||||
})
|
||||
switch e := err.(type) {
|
||||
case roomserverAPI.ErrInvalidID:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.Unknown(e.Error()),
|
||||
}
|
||||
case roomserverAPI.ErrNotAllowed:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: spec.Forbidden(e.Error()),
|
||||
}
|
||||
case nil:
|
||||
default:
|
||||
util.GetLogger(ctx).WithError(err).Error("PerformInvite failed")
|
||||
sentry.CaptureException(err)
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if r.Visibility == spec.Public {
|
||||
// expose this room in the published room list
|
||||
if err = rsAPI.PerformPublish(ctx, &roomserverAPI.PerformPublishRequest{
|
||||
RoomID: roomID,
|
||||
Visibility: spec.Public,
|
||||
}); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("failed to publish room")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
roomAlias, createRes := rsAPI.PerformCreateRoom(ctx, *userID, *roomID, &req)
|
||||
if createRes != nil {
|
||||
return *createRes
|
||||
}
|
||||
|
||||
response := createRoomResponse{
|
||||
RoomID: roomID,
|
||||
RoomID: roomID.String(),
|
||||
RoomAlias: roomAlias,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
|
||||
"github.com/matrix-org/dendrite/appservice"
|
||||
"github.com/matrix-org/dendrite/roomserver"
|
||||
|
|
@ -63,7 +64,7 @@ func TestJoinRoomByIDOrAlias(t *testing.T) {
|
|||
IsDirect: true,
|
||||
Topic: "testing",
|
||||
Visibility: "public",
|
||||
Preset: presetPublicChat,
|
||||
Preset: spec.PresetPublicChat,
|
||||
RoomAliasName: "alias",
|
||||
Invite: []string{bob.ID},
|
||||
}, aliceDev, &cfg.ClientAPI, userAPI, rsAPI, asAPI, time.Now())
|
||||
|
|
@ -78,7 +79,7 @@ func TestJoinRoomByIDOrAlias(t *testing.T) {
|
|||
IsDirect: true,
|
||||
Topic: "testing",
|
||||
Visibility: "public",
|
||||
Preset: presetPublicChat,
|
||||
Preset: spec.PresetPublicChat,
|
||||
Invite: []string{charlie.ID},
|
||||
}, aliceDev, &cfg.ClientAPI, userAPI, rsAPI, asAPI, time.Now())
|
||||
crRespWithGuestAccess, ok := resp.JSON.(createRoomResponse)
|
||||
|
|
|
|||
|
|
@ -16,12 +16,14 @@ package routing
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
|
||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||
|
|
@ -308,6 +310,41 @@ func sendInvite(
|
|||
}, nil
|
||||
}
|
||||
|
||||
func buildMembershipEventDirect(
|
||||
ctx context.Context,
|
||||
targetUserID, reason string, userDisplayName, userAvatarURL string,
|
||||
sender string, senderDomain spec.ServerName,
|
||||
membership, roomID string, isDirect bool,
|
||||
keyID gomatrixserverlib.KeyID, privateKey ed25519.PrivateKey, evTime time.Time,
|
||||
rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||
) (*types.HeaderedEvent, error) {
|
||||
proto := gomatrixserverlib.ProtoEvent{
|
||||
Sender: sender,
|
||||
RoomID: roomID,
|
||||
Type: "m.room.member",
|
||||
StateKey: &targetUserID,
|
||||
}
|
||||
|
||||
content := gomatrixserverlib.MemberContent{
|
||||
Membership: membership,
|
||||
DisplayName: userDisplayName,
|
||||
AvatarURL: userAvatarURL,
|
||||
Reason: reason,
|
||||
IsDirect: isDirect,
|
||||
}
|
||||
|
||||
if err := proto.SetContent(content); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
identity := &fclient.SigningIdentity{
|
||||
ServerName: senderDomain,
|
||||
KeyID: keyID,
|
||||
PrivateKey: privateKey,
|
||||
}
|
||||
return eventutil.QueryAndBuildEvent(ctx, &proto, identity, evTime, rsAPI, nil)
|
||||
}
|
||||
|
||||
func buildMembershipEvent(
|
||||
ctx context.Context,
|
||||
targetUserID, reason string, profileAPI userapi.ClientUserAPI,
|
||||
|
|
@ -321,31 +358,13 @@ func buildMembershipEvent(
|
|||
return nil, err
|
||||
}
|
||||
|
||||
proto := gomatrixserverlib.ProtoEvent{
|
||||
Sender: device.UserID,
|
||||
RoomID: roomID,
|
||||
Type: "m.room.member",
|
||||
StateKey: &targetUserID,
|
||||
}
|
||||
|
||||
content := gomatrixserverlib.MemberContent{
|
||||
Membership: membership,
|
||||
DisplayName: profile.DisplayName,
|
||||
AvatarURL: profile.AvatarURL,
|
||||
Reason: reason,
|
||||
IsDirect: isDirect,
|
||||
}
|
||||
|
||||
if err = proto.SetContent(content); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
identity, err := cfg.Matrix.SigningIdentityFor(device.UserDomain())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return eventutil.QueryAndBuildEvent(ctx, &proto, cfg.Matrix, identity, evTime, rsAPI, nil)
|
||||
return buildMembershipEventDirect(ctx, targetUserID, reason, profile.DisplayName, profile.AvatarURL,
|
||||
device.UserID, device.UserDomain(), membership, roomID, isDirect, identity.KeyID, identity.PrivateKey, evTime, rsAPI)
|
||||
}
|
||||
|
||||
// loadProfile lookups the profile of a given user from the database and returns
|
||||
|
|
|
|||
|
|
@ -387,7 +387,7 @@ func buildMembershipEvents(
|
|||
return nil, err
|
||||
}
|
||||
|
||||
event, err := eventutil.QueryAndBuildEvent(ctx, &proto, cfg.Matrix, identity, evTime, rsAPI, nil)
|
||||
event, err := eventutil.QueryAndBuildEvent(ctx, &proto, identity, evTime, rsAPI, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ func SendRedaction(
|
|||
}
|
||||
|
||||
var queryRes roomserverAPI.QueryLatestEventsAndStateResponse
|
||||
e, err := eventutil.QueryAndBuildEvent(req.Context(), &proto, cfg.Matrix, identity, time.Now(), rsAPI, &queryRes)
|
||||
e, err := eventutil.QueryAndBuildEvent(req.Context(), &proto, identity, time.Now(), rsAPI, &queryRes)
|
||||
if errors.Is(err, eventutil.ErrRoomNoExists{}) {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ func generateSendEvent(
|
|||
}
|
||||
|
||||
var queryRes api.QueryLatestEventsAndStateResponse
|
||||
e, err := eventutil.QueryAndBuildEvent(ctx, &proto, cfg.Matrix, identity, evTime, rsAPI, &queryRes)
|
||||
e, err := eventutil.QueryAndBuildEvent(ctx, &proto, identity, evTime, rsAPI, &queryRes)
|
||||
switch specificErr := err.(type) {
|
||||
case nil:
|
||||
case eventutil.ErrRoomNoExists:
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ func SendServerNotice(
|
|||
Invite: []string{r.UserID},
|
||||
Name: cfgNotices.RoomName,
|
||||
Visibility: "private",
|
||||
Preset: presetPrivateChat,
|
||||
Preset: spec.PresetPrivateChat,
|
||||
CreationContent: cc,
|
||||
RoomVersion: roomVersion,
|
||||
PowerLevelContentOverride: pl,
|
||||
|
|
|
|||
|
|
@ -380,7 +380,7 @@ func emit3PIDInviteEvent(
|
|||
}
|
||||
|
||||
queryRes := api.QueryLatestEventsAndStateResponse{}
|
||||
event, err := eventutil.QueryAndBuildEvent(ctx, proto, cfg.Matrix, identity, evTime, rsAPI, &queryRes)
|
||||
event, err := eventutil.QueryAndBuildEvent(ctx, proto, identity, evTime, rsAPI, &queryRes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/matrix-org/gomatrix"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
)
|
||||
|
||||
const userPassword = "this_is_a_long_password"
|
||||
|
|
@ -56,7 +57,7 @@ func runTests(baseURL string, v *semver.Version) error {
|
|||
|
||||
// create DM room, join it and exchange messages
|
||||
createRoomResp, err := users[0].client.CreateRoom(&gomatrix.ReqCreateRoom{
|
||||
Preset: "trusted_private_chat",
|
||||
Preset: spec.PresetTrustedPrivateChat,
|
||||
Invite: []string{users[1].userID},
|
||||
IsDirect: true,
|
||||
})
|
||||
|
|
@ -98,7 +99,7 @@ func runTests(baseURL string, v *semver.Version) error {
|
|||
publicRoomID := ""
|
||||
createRoomResp, err = users[0].client.CreateRoom(&gomatrix.ReqCreateRoom{
|
||||
RoomAliasName: "global",
|
||||
Preset: "public_chat",
|
||||
Preset: spec.PresetPublicChat,
|
||||
})
|
||||
if err != nil { // this is okay and expected if the room already exists and the aliases clash
|
||||
// try to join it
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ type RoomserverFederationAPI interface {
|
|||
// Handle an instruction to make_leave & send_leave with a remote server.
|
||||
PerformLeave(ctx context.Context, request *PerformLeaveRequest, response *PerformLeaveResponse) error
|
||||
// Handle sending an invite to a remote server.
|
||||
PerformInvite(ctx context.Context, request *PerformInviteRequest, response *PerformInviteResponse) error
|
||||
SendInvite(ctx context.Context, event gomatrixserverlib.PDU, strippedState []gomatrixserverlib.InviteStrippedState) (gomatrixserverlib.PDU, error)
|
||||
// Handle an instruction to peek a room on a remote server.
|
||||
PerformOutboundPeek(ctx context.Context, request *PerformOutboundPeekRequest, response *PerformOutboundPeekResponse) error
|
||||
// Query the server names of the joined hosts in a room.
|
||||
|
|
@ -190,9 +190,9 @@ type PerformLeaveResponse struct {
|
|||
}
|
||||
|
||||
type PerformInviteRequest struct {
|
||||
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
|
||||
Event *rstypes.HeaderedEvent `json:"event"`
|
||||
InviteRoomState []fclient.InviteV2StrippedState `json:"invite_room_state"`
|
||||
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
|
||||
Event *rstypes.HeaderedEvent `json:"event"`
|
||||
InviteRoomState []gomatrixserverlib.InviteStrippedState `json:"invite_room_state"`
|
||||
}
|
||||
|
||||
type PerformInviteResponse struct {
|
||||
|
|
|
|||
|
|
@ -503,60 +503,59 @@ func (r *FederationInternalAPI) PerformLeave(
|
|||
)
|
||||
}
|
||||
|
||||
// PerformLeaveRequest implements api.FederationInternalAPI
|
||||
func (r *FederationInternalAPI) PerformInvite(
|
||||
// SendInvite implements api.FederationInternalAPI
|
||||
func (r *FederationInternalAPI) SendInvite(
|
||||
ctx context.Context,
|
||||
request *api.PerformInviteRequest,
|
||||
response *api.PerformInviteResponse,
|
||||
) (err error) {
|
||||
_, origin, err := r.cfg.Matrix.SplitLocalID('@', request.Event.Sender())
|
||||
event gomatrixserverlib.PDU,
|
||||
strippedState []gomatrixserverlib.InviteStrippedState,
|
||||
) (gomatrixserverlib.PDU, error) {
|
||||
_, origin, err := r.cfg.Matrix.SplitLocalID('@', event.Sender())
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if request.Event.StateKey() == nil {
|
||||
return errors.New("invite must be a state event")
|
||||
if event.StateKey() == nil {
|
||||
return nil, errors.New("invite must be a state event")
|
||||
}
|
||||
|
||||
_, destination, err := gomatrixserverlib.SplitID('@', *request.Event.StateKey())
|
||||
_, destination, err := gomatrixserverlib.SplitID('@', *event.StateKey())
|
||||
if err != nil {
|
||||
return fmt.Errorf("gomatrixserverlib.SplitID: %w", err)
|
||||
return nil, fmt.Errorf("gomatrixserverlib.SplitID: %w", err)
|
||||
}
|
||||
|
||||
// TODO (devon): This should be allowed via a relay. Currently only transactions
|
||||
// can be sent to relays. Would need to extend relays to handle invites.
|
||||
if !r.shouldAttemptDirectFederation(destination) {
|
||||
return fmt.Errorf("relay servers have no meaningful response for invite.")
|
||||
return nil, fmt.Errorf("relay servers have no meaningful response for invite.")
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"event_id": request.Event.EventID(),
|
||||
"user_id": *request.Event.StateKey(),
|
||||
"room_id": request.Event.RoomID(),
|
||||
"room_version": request.RoomVersion,
|
||||
"event_id": event.EventID(),
|
||||
"user_id": *event.StateKey(),
|
||||
"room_id": event.RoomID(),
|
||||
"room_version": event.Version(),
|
||||
"destination": destination,
|
||||
}).Info("Sending invite")
|
||||
|
||||
inviteReq, err := fclient.NewInviteV2Request(request.Event.PDU, request.InviteRoomState)
|
||||
inviteReq, err := fclient.NewInviteV2Request(event, strippedState)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gomatrixserverlib.NewInviteV2Request: %w", err)
|
||||
return nil, fmt.Errorf("gomatrixserverlib.NewInviteV2Request: %w", err)
|
||||
}
|
||||
|
||||
inviteRes, err := r.federation.SendInviteV2(ctx, origin, destination, inviteReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("r.federation.SendInviteV2: failed to send invite: %w", err)
|
||||
return nil, fmt.Errorf("r.federation.SendInviteV2: failed to send invite: %w", err)
|
||||
}
|
||||
verImpl, err := gomatrixserverlib.GetRoomVersion(request.RoomVersion)
|
||||
verImpl, err := gomatrixserverlib.GetRoomVersion(event.Version())
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inviteEvent, err := verImpl.NewEventFromUntrustedJSON(inviteRes.Event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("r.federation.SendInviteV2 failed to decode event response: %w", err)
|
||||
return nil, fmt.Errorf("r.federation.SendInviteV2 failed to decode event response: %w", err)
|
||||
}
|
||||
response.Event = &types.HeaderedEvent{PDU: inviteEvent}
|
||||
return nil
|
||||
return inviteEvent, nil
|
||||
}
|
||||
|
||||
// PerformServersAlive implements api.FederationInternalAPI
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/roomserver/types"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
|
|
@ -34,7 +33,7 @@ import (
|
|||
func InviteV2(
|
||||
httpReq *http.Request,
|
||||
request *fclient.FederationRequest,
|
||||
roomID string,
|
||||
roomID spec.RoomID,
|
||||
eventID string,
|
||||
cfg *config.FederationAPI,
|
||||
rsAPI api.FederationRoomserverAPI,
|
||||
|
|
@ -56,9 +55,55 @@ func InviteV2(
|
|||
JSON: spec.BadJSON(err.Error()),
|
||||
}
|
||||
case nil:
|
||||
return processInvite(
|
||||
httpReq.Context(), true, inviteReq.Event(), inviteReq.RoomVersion(), inviteReq.InviteRoomState(), roomID, eventID, cfg, rsAPI, keys,
|
||||
)
|
||||
if inviteReq.Event().StateKey() == nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.BadJSON("The invite event has no state key"),
|
||||
}
|
||||
}
|
||||
|
||||
invitedUser, userErr := spec.NewUserID(*inviteReq.Event().StateKey(), true)
|
||||
if userErr != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.InvalidParam("The user ID is invalid"),
|
||||
}
|
||||
}
|
||||
if !cfg.Matrix.IsLocalServerName(invitedUser.Domain()) {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.InvalidParam("The invited user domain does not belong to this server"),
|
||||
}
|
||||
}
|
||||
|
||||
if inviteReq.Event().EventID() != eventID {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.BadJSON("The event ID in the request path must match the event ID in the invite event JSON"),
|
||||
}
|
||||
}
|
||||
|
||||
input := gomatrixserverlib.HandleInviteInput{
|
||||
RoomVersion: inviteReq.RoomVersion(),
|
||||
RoomID: roomID,
|
||||
InvitedUser: *invitedUser,
|
||||
KeyID: cfg.Matrix.KeyID,
|
||||
PrivateKey: cfg.Matrix.PrivateKey,
|
||||
Verifier: keys,
|
||||
RoomQuerier: rsAPI,
|
||||
MembershipQuerier: &api.MembershipQuerier{Roomserver: rsAPI},
|
||||
StateQuerier: rsAPI.StateQuerier(),
|
||||
InviteEvent: inviteReq.Event(),
|
||||
StrippedState: inviteReq.InviteRoomState(),
|
||||
}
|
||||
event, jsonErr := handleInvite(httpReq.Context(), input, rsAPI)
|
||||
if jsonErr != nil {
|
||||
return *jsonErr
|
||||
}
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: fclient.RespInviteV2{Event: event.JSON()},
|
||||
}
|
||||
default:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -71,7 +116,7 @@ func InviteV2(
|
|||
func InviteV1(
|
||||
httpReq *http.Request,
|
||||
request *fclient.FederationRequest,
|
||||
roomID string,
|
||||
roomID spec.RoomID,
|
||||
eventID string,
|
||||
cfg *config.FederationAPI,
|
||||
rsAPI api.FederationRoomserverAPI,
|
||||
|
|
@ -94,55 +139,11 @@ func InviteV1(
|
|||
JSON: spec.NotJSON("The request body could not be decoded into an invite v1 request. " + err.Error()),
|
||||
}
|
||||
}
|
||||
var strippedState []fclient.InviteV2StrippedState
|
||||
if err := json.Unmarshal(event.Unsigned(), &strippedState); err != nil {
|
||||
var strippedState []gomatrixserverlib.InviteStrippedState
|
||||
if jsonErr := json.Unmarshal(event.Unsigned(), &strippedState); jsonErr != nil {
|
||||
// just warn, they may not have added any.
|
||||
util.GetLogger(httpReq.Context()).Warnf("failed to extract stripped state from invite event")
|
||||
}
|
||||
return processInvite(
|
||||
httpReq.Context(), false, event, roomVer, strippedState, roomID, eventID, cfg, rsAPI, keys,
|
||||
)
|
||||
}
|
||||
|
||||
func processInvite(
|
||||
ctx context.Context,
|
||||
isInviteV2 bool,
|
||||
event gomatrixserverlib.PDU,
|
||||
roomVer gomatrixserverlib.RoomVersion,
|
||||
strippedState []fclient.InviteV2StrippedState,
|
||||
roomID string,
|
||||
eventID string,
|
||||
cfg *config.FederationAPI,
|
||||
rsAPI api.FederationRoomserverAPI,
|
||||
keys gomatrixserverlib.JSONVerifier,
|
||||
) util.JSONResponse {
|
||||
|
||||
// Check that we can accept invites for this room version.
|
||||
verImpl, err := gomatrixserverlib.GetRoomVersion(roomVer)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.UnsupportedRoomVersion(
|
||||
fmt.Sprintf("Room version %q is not supported by this server.", roomVer),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the room ID is correct.
|
||||
if event.RoomID() != roomID {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.BadJSON("The room ID in the request path must match the room ID in the invite event JSON"),
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the event ID is correct.
|
||||
if event.EventID() != eventID {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.BadJSON("The event ID in the request path must match the event ID in the invite event JSON"),
|
||||
}
|
||||
}
|
||||
|
||||
if event.StateKey() == nil {
|
||||
return util.JSONResponse{
|
||||
|
|
@ -151,105 +152,91 @@ func processInvite(
|
|||
}
|
||||
}
|
||||
|
||||
_, domain, err := cfg.Matrix.SplitLocalID('@', *event.StateKey())
|
||||
invitedUser, err := spec.NewUserID(*event.StateKey(), true)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.InvalidParam(fmt.Sprintf("The user ID is invalid or domain %q does not belong to this server", domain)),
|
||||
JSON: spec.InvalidParam("The user ID is invalid"),
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the event is signed by the server sending the request.
|
||||
redacted, err := verImpl.RedactEventJSON(event.JSON())
|
||||
if err != nil {
|
||||
if !cfg.Matrix.IsLocalServerName(invitedUser.Domain()) {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.BadJSON("The event JSON could not be redacted"),
|
||||
JSON: spec.InvalidParam("The invited user domain does not belong to this server"),
|
||||
}
|
||||
}
|
||||
_, serverName, err := gomatrixserverlib.SplitID('@', event.Sender())
|
||||
if err != nil {
|
||||
|
||||
if event.EventID() != eventID {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.BadJSON("The event JSON contains an invalid sender"),
|
||||
}
|
||||
}
|
||||
verifyRequests := []gomatrixserverlib.VerifyJSONRequest{{
|
||||
ServerName: serverName,
|
||||
Message: redacted,
|
||||
AtTS: event.OriginServerTS(),
|
||||
ValidityCheckingFunc: gomatrixserverlib.StrictValiditySignatureCheck,
|
||||
}}
|
||||
verifyResults, err := keys.VerifyJSONs(ctx, verifyRequests)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("keys.VerifyJSONs failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
if verifyResults[0].Error != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: spec.Forbidden("The invite must be signed by the server it originated on"),
|
||||
JSON: spec.BadJSON("The event ID in the request path must match the event ID in the invite event JSON"),
|
||||
}
|
||||
}
|
||||
|
||||
// Sign the event so that other servers will know that we have received the invite.
|
||||
signedEvent := event.Sign(
|
||||
string(domain), cfg.Matrix.KeyID, cfg.Matrix.PrivateKey,
|
||||
)
|
||||
|
||||
// Add the invite event to the Roomserver.
|
||||
inviteEvent := &types.HeaderedEvent{PDU: signedEvent}
|
||||
request := &api.PerformInviteRequest{
|
||||
Event: inviteEvent,
|
||||
InviteRoomState: strippedState,
|
||||
RoomVersion: inviteEvent.Version(),
|
||||
SendAsServer: string(api.DoNotSendToOtherServers),
|
||||
TransactionID: nil,
|
||||
input := gomatrixserverlib.HandleInviteInput{
|
||||
RoomVersion: roomVer,
|
||||
RoomID: roomID,
|
||||
InvitedUser: *invitedUser,
|
||||
KeyID: cfg.Matrix.KeyID,
|
||||
PrivateKey: cfg.Matrix.PrivateKey,
|
||||
Verifier: keys,
|
||||
RoomQuerier: rsAPI,
|
||||
MembershipQuerier: &api.MembershipQuerier{Roomserver: rsAPI},
|
||||
StateQuerier: rsAPI.StateQuerier(),
|
||||
InviteEvent: event,
|
||||
StrippedState: strippedState,
|
||||
}
|
||||
|
||||
if err = rsAPI.PerformInvite(ctx, request); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("PerformInvite failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
event, jsonErr := handleInvite(httpReq.Context(), input, rsAPI)
|
||||
if jsonErr != nil {
|
||||
return *jsonErr
|
||||
}
|
||||
|
||||
switch e := err.(type) {
|
||||
case api.ErrInvalidID:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.Unknown(e.Error()),
|
||||
}
|
||||
case api.ErrNotAllowed:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: spec.Forbidden(e.Error()),
|
||||
}
|
||||
case nil:
|
||||
default:
|
||||
util.GetLogger(ctx).WithError(err).Error("PerformInvite failed")
|
||||
sentry.CaptureException(err)
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
// Return the signed event to the originating server, it should then tell
|
||||
// the other servers in the room that we have been invited.
|
||||
if isInviteV2 {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: fclient.RespInviteV2{Event: signedEvent.JSON()},
|
||||
}
|
||||
} else {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: fclient.RespInvite{Event: signedEvent.JSON()},
|
||||
}
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: fclient.RespInvite{Event: event.JSON()},
|
||||
}
|
||||
}
|
||||
|
||||
func handleInvite(ctx context.Context, input gomatrixserverlib.HandleInviteInput, rsAPI api.FederationRoomserverAPI) (gomatrixserverlib.PDU, *util.JSONResponse) {
|
||||
inviteEvent, err := gomatrixserverlib.HandleInvite(ctx, input)
|
||||
switch e := err.(type) {
|
||||
case nil:
|
||||
case spec.InternalServerError:
|
||||
util.GetLogger(ctx).WithError(err)
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
case spec.MatrixError:
|
||||
util.GetLogger(ctx).WithError(err)
|
||||
code := http.StatusInternalServerError
|
||||
switch e.ErrCode {
|
||||
case spec.ErrorForbidden:
|
||||
code = http.StatusForbidden
|
||||
case spec.ErrorUnsupportedRoomVersion:
|
||||
fallthrough // http.StatusBadRequest
|
||||
case spec.ErrorBadJSON:
|
||||
code = http.StatusBadRequest
|
||||
}
|
||||
|
||||
return nil, &util.JSONResponse{
|
||||
Code: code,
|
||||
JSON: e,
|
||||
}
|
||||
default:
|
||||
util.GetLogger(ctx).WithError(err)
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.Unknown("unknown error"),
|
||||
}
|
||||
}
|
||||
|
||||
headeredInvite := &types.HeaderedEvent{PDU: inviteEvent}
|
||||
if err = rsAPI.HandleInvite(ctx, headeredInvite); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("HandleInvite failed")
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
return inviteEvent, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
package routing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
|
@ -74,7 +73,7 @@ func MakeJoin(
|
|||
queryRes := api.QueryLatestEventsAndStateResponse{
|
||||
RoomVersion: roomVersion,
|
||||
}
|
||||
event, err := eventutil.QueryAndBuildEvent(httpReq.Context(), proto, cfg.Matrix, identity, time.Now(), rsAPI, &queryRes)
|
||||
event, err := eventutil.QueryAndBuildEvent(httpReq.Context(), proto, identity, time.Now(), rsAPI, &queryRes)
|
||||
switch e := err.(type) {
|
||||
case nil:
|
||||
case eventutil.ErrRoomNoExists:
|
||||
|
|
@ -169,25 +168,6 @@ func MakeJoin(
|
|||
}
|
||||
}
|
||||
|
||||
type MembershipQuerier struct {
|
||||
roomserver api.FederationRoomserverAPI
|
||||
}
|
||||
|
||||
func (mq *MembershipQuerier) CurrentMembership(ctx context.Context, roomID spec.RoomID, userID spec.UserID) (string, error) {
|
||||
req := api.QueryMembershipForUserRequest{
|
||||
RoomID: roomID.String(),
|
||||
UserID: userID.String(),
|
||||
}
|
||||
res := api.QueryMembershipForUserResponse{}
|
||||
err := mq.roomserver.QueryMembershipForUser(ctx, &req, &res)
|
||||
|
||||
membership := ""
|
||||
if err == nil {
|
||||
membership = res.Membership
|
||||
}
|
||||
return membership, err
|
||||
}
|
||||
|
||||
// SendJoin implements the /send_join API
|
||||
// The make-join send-join dance makes much more sense as a single
|
||||
// flow so the cyclomatic complexity is high:
|
||||
|
|
@ -221,7 +201,7 @@ func SendJoin(
|
|||
KeyID: cfg.Matrix.KeyID,
|
||||
PrivateKey: cfg.Matrix.PrivateKey,
|
||||
Verifier: keys,
|
||||
MembershipQuerier: &MembershipQuerier{roomserver: rsAPI},
|
||||
MembershipQuerier: &api.MembershipQuerier{Roomserver: rsAPI},
|
||||
}
|
||||
response, joinErr := gomatrixserverlib.HandleSendJoin(input)
|
||||
switch e := joinErr.(type) {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ func MakeLeave(
|
|||
}
|
||||
|
||||
queryRes := api.QueryLatestEventsAndStateResponse{}
|
||||
event, err := eventutil.QueryAndBuildEvent(httpReq.Context(), proto, cfg.Matrix, identity, time.Now(), rsAPI, &queryRes)
|
||||
event, err := eventutil.QueryAndBuildEvent(httpReq.Context(), proto, identity, time.Now(), rsAPI, &queryRes)
|
||||
switch e := err.(type) {
|
||||
case nil:
|
||||
case eventutil.ErrRoomNoExists:
|
||||
|
|
|
|||
|
|
@ -152,8 +152,16 @@ func Setup(
|
|||
JSON: spec.Forbidden("Forbidden by server ACLs"),
|
||||
}
|
||||
}
|
||||
|
||||
roomID, err := spec.NewRoomID(vars["roomID"])
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.InvalidParam("Invalid RoomID"),
|
||||
}
|
||||
}
|
||||
return InviteV1(
|
||||
httpReq, request, vars["roomID"], vars["eventID"],
|
||||
httpReq, request, *roomID, vars["eventID"],
|
||||
cfg, rsAPI, keys,
|
||||
)
|
||||
},
|
||||
|
|
@ -168,8 +176,16 @@ func Setup(
|
|||
JSON: spec.Forbidden("Forbidden by server ACLs"),
|
||||
}
|
||||
}
|
||||
|
||||
roomID, err := spec.NewRoomID(vars["roomID"])
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.InvalidParam("Invalid RoomID"),
|
||||
}
|
||||
}
|
||||
return InviteV2(
|
||||
httpReq, request, vars["roomID"], vars["eventID"],
|
||||
httpReq, request, *roomID, vars["eventID"],
|
||||
cfg, rsAPI, keys,
|
||||
)
|
||||
},
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -22,7 +22,7 @@ require (
|
|||
github.com/matrix-org/dugong v0.0.0-20210921133753-66e6b1c67e2e
|
||||
github.com/matrix-org/go-sqlite3-js v0.0.0-20220419092513-28aa791a1c91
|
||||
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530
|
||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20230531142456-fa1aa09094c0
|
||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20230531155817-0e3adf17bee6
|
||||
github.com/matrix-org/pinecone v0.11.1-0.20230210171230-8c3b24f2649a
|
||||
github.com/matrix-org/util v0.0.0-20221111132719-399730281e66
|
||||
github.com/mattn/go-sqlite3 v1.14.16
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -323,8 +323,8 @@ github.com/matrix-org/go-sqlite3-js v0.0.0-20220419092513-28aa791a1c91 h1:s7fexw
|
|||
github.com/matrix-org/go-sqlite3-js v0.0.0-20220419092513-28aa791a1c91/go.mod h1:e+cg2q7C7yE5QnAXgzo512tgFh1RbQLC0+jozuegKgo=
|
||||
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530 h1:kHKxCOLcHH8r4Fzarl4+Y3K5hjothkVW5z7T1dUM11U=
|
||||
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s=
|
||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20230531142456-fa1aa09094c0 h1:I7suzjSCl5uh6Ac7iM3IEoJch8SFi/trJOct5j0ycqA=
|
||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20230531142456-fa1aa09094c0/go.mod h1:H9V9N3Uqn1bBJqYJNGK1noqtgJTaCEhtTdcH/mp50uU=
|
||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20230531155817-0e3adf17bee6 h1:Kh1TNvJDhWN5CdgtICNUC4G0wV2km51LGr46Dvl153A=
|
||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20230531155817-0e3adf17bee6/go.mod h1:H9V9N3Uqn1bBJqYJNGK1noqtgJTaCEhtTdcH/mp50uU=
|
||||
github.com/matrix-org/pinecone v0.11.1-0.20230210171230-8c3b24f2649a h1:awrPDf9LEFySxTLKYBMCiObelNx/cBuv/wzllvCCH3A=
|
||||
github.com/matrix-org/pinecone v0.11.1-0.20230210171230-8c3b24f2649a/go.mod h1:HchJX9oKMXaT2xYFs0Ha/6Zs06mxLU8k6F1ODnrGkeQ=
|
||||
github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 h1:6z4KxomXSIGWqhHcfzExgkH3Z3UkIXry4ibJS4Aqz2Y=
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import (
|
|||
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/roomserver/types"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
|
||||
|
|
@ -51,7 +50,7 @@ func (e ErrRoomNoExists) Unwrap() error {
|
|||
// Returns an error if something else went wrong
|
||||
func QueryAndBuildEvent(
|
||||
ctx context.Context,
|
||||
proto *gomatrixserverlib.ProtoEvent, cfg *config.Global,
|
||||
proto *gomatrixserverlib.ProtoEvent,
|
||||
identity *fclient.SigningIdentity, evTime time.Time,
|
||||
rsAPI api.QueryLatestEventsAndStateAPI, queryRes *api.QueryLatestEventsAndStateResponse,
|
||||
) (*types.HeaderedEvent, error) {
|
||||
|
|
@ -64,14 +63,14 @@ func QueryAndBuildEvent(
|
|||
// This can pass through a ErrRoomNoExists to the caller
|
||||
return nil, err
|
||||
}
|
||||
return BuildEvent(ctx, proto, cfg, identity, evTime, eventsNeeded, queryRes)
|
||||
return BuildEvent(ctx, proto, identity, evTime, eventsNeeded, queryRes)
|
||||
}
|
||||
|
||||
// BuildEvent builds a Matrix event from the builder and QueryLatestEventsAndStateResponse
|
||||
// provided.
|
||||
func BuildEvent(
|
||||
ctx context.Context,
|
||||
proto *gomatrixserverlib.ProtoEvent, cfg *config.Global,
|
||||
proto *gomatrixserverlib.ProtoEvent,
|
||||
identity *fclient.SigningIdentity, evTime time.Time,
|
||||
eventsNeeded *gomatrixserverlib.StateNeeded, queryRes *api.QueryLatestEventsAndStateResponse,
|
||||
) (*types.HeaderedEvent, error) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
|
||||
asAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||
fsAPI "github.com/matrix-org/dendrite/federationapi/api"
|
||||
|
|
@ -179,6 +180,7 @@ type ClientRoomserverAPI interface {
|
|||
GetRoomIDForAlias(ctx context.Context, req *GetRoomIDForAliasRequest, res *GetRoomIDForAliasResponse) error
|
||||
GetAliasesForRoomID(ctx context.Context, req *GetAliasesForRoomIDRequest, res *GetAliasesForRoomIDResponse) error
|
||||
|
||||
PerformCreateRoom(ctx context.Context, userID spec.UserID, roomID spec.RoomID, createRequest *PerformCreateRoomRequest) (string, *util.JSONResponse)
|
||||
// PerformRoomUpgrade upgrades a room to a newer version
|
||||
PerformRoomUpgrade(ctx context.Context, roomID, userID string, roomVersion gomatrixserverlib.RoomVersion) (newRoomID string, err error)
|
||||
PerformAdminEvacuateRoom(ctx context.Context, roomID string) (affected []string, err error)
|
||||
|
|
@ -234,6 +236,8 @@ type FederationRoomserverAPI interface {
|
|||
QueryRoomsForUser(ctx context.Context, req *QueryRoomsForUserRequest, res *QueryRoomsForUserResponse) error
|
||||
QueryRestrictedJoinAllowed(ctx context.Context, roomID spec.RoomID, userID spec.UserID) (string, error)
|
||||
PerformInboundPeek(ctx context.Context, req *PerformInboundPeekRequest, res *PerformInboundPeekResponse) error
|
||||
HandleInvite(ctx context.Context, event *types.HeaderedEvent) error
|
||||
|
||||
PerformInvite(ctx context.Context, req *PerformInviteRequest) error
|
||||
// Query a given amount (or less) of events prior to a given set of events.
|
||||
PerformBackfill(ctx context.Context, req *PerformBackfillRequest, res *PerformBackfillResponse) error
|
||||
|
|
@ -243,6 +247,9 @@ type FederationRoomserverAPI interface {
|
|||
QueryRoomInfo(ctx context.Context, roomID spec.RoomID) (*types.RoomInfo, error)
|
||||
UserJoinedToRoom(ctx context.Context, roomID types.RoomNID, userID spec.UserID) (bool, error)
|
||||
LocallyJoinedUsers(ctx context.Context, roomVersion gomatrixserverlib.RoomVersion, roomNID types.RoomNID) ([]gomatrixserverlib.PDU, error)
|
||||
|
||||
IsKnownRoom(ctx context.Context, roomID spec.RoomID) (bool, error)
|
||||
StateQuerier() gomatrixserverlib.StateQuerier
|
||||
}
|
||||
|
||||
type KeyserverRoomserverAPI interface {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,36 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/roomserver/types"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
type PerformCreateRoomRequest struct {
|
||||
InvitedUsers []string
|
||||
RoomName string
|
||||
Visibility string
|
||||
Topic string
|
||||
StatePreset string
|
||||
CreationContent json.RawMessage
|
||||
InitialState []gomatrixserverlib.FledglingEvent
|
||||
RoomAliasName string
|
||||
RoomVersion gomatrixserverlib.RoomVersion
|
||||
PowerLevelContentOverride json.RawMessage
|
||||
IsDirect bool
|
||||
|
||||
UserDisplayName string
|
||||
UserAvatarURL string
|
||||
KeyID gomatrixserverlib.KeyID
|
||||
PrivateKey ed25519.PrivateKey
|
||||
EventTime time.Time
|
||||
}
|
||||
|
||||
type PerformJoinRequest struct {
|
||||
RoomIDOrAlias string `json:"room_id_or_alias"`
|
||||
UserID string `json:"user_id"`
|
||||
|
|
@ -28,11 +51,11 @@ type PerformLeaveResponse struct {
|
|||
}
|
||||
|
||||
type PerformInviteRequest struct {
|
||||
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
|
||||
Event *types.HeaderedEvent `json:"event"`
|
||||
InviteRoomState []fclient.InviteV2StrippedState `json:"invite_room_state"`
|
||||
SendAsServer string `json:"send_as_server"`
|
||||
TransactionID *TransactionID `json:"transaction_id"`
|
||||
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
|
||||
Event *types.HeaderedEvent `json:"event"`
|
||||
InviteRoomState []gomatrixserverlib.InviteStrippedState `json:"invite_room_state"`
|
||||
SendAsServer string `json:"send_as_server"`
|
||||
TransactionID *TransactionID `json:"transaction_id"`
|
||||
}
|
||||
|
||||
type PerformPeekRequest struct {
|
||||
|
|
|
|||
|
|
@ -486,3 +486,22 @@ func (rq *JoinRoomQuerier) RestrictedRoomJoinInfo(ctx context.Context, roomID sp
|
|||
JoinedUsers: locallyJoinedUsers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type MembershipQuerier struct {
|
||||
Roomserver FederationRoomserverAPI
|
||||
}
|
||||
|
||||
func (mq *MembershipQuerier) CurrentMembership(ctx context.Context, roomID spec.RoomID, userID spec.UserID) (string, error) {
|
||||
req := QueryMembershipForUserRequest{
|
||||
RoomID: roomID.String(),
|
||||
UserID: userID.String(),
|
||||
}
|
||||
res := QueryMembershipForUserResponse{}
|
||||
err := mq.Roomserver.QueryMembershipForUser(ctx, &req, &res)
|
||||
|
||||
membership := ""
|
||||
if err == nil {
|
||||
membership = res.Membership
|
||||
}
|
||||
return membership, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ func (r *RoomserverInternalAPI) RemoveRoomAlias(
|
|||
return err
|
||||
}
|
||||
|
||||
newEvent, err := eventutil.BuildEvent(ctx, proto, &r.Cfg.Global, identity, time.Now(), &eventsNeeded, stateRes)
|
||||
newEvent, err := eventutil.BuildEvent(ctx, proto, identity, time.Now(), &eventsNeeded, stateRes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/getsentry/sentry-go"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
|
|
@ -19,6 +20,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/roomserver/internal/query"
|
||||
"github.com/matrix-org/dendrite/roomserver/producers"
|
||||
"github.com/matrix-org/dendrite/roomserver/storage"
|
||||
"github.com/matrix-org/dendrite/roomserver/types"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
|
|
@ -40,6 +42,7 @@ type RoomserverInternalAPI struct {
|
|||
*perform.Forgetter
|
||||
*perform.Upgrader
|
||||
*perform.Admin
|
||||
*perform.Creator
|
||||
ProcessContext *process.ProcessContext
|
||||
DB storage.Database
|
||||
Cfg *config.Dendrite
|
||||
|
|
@ -131,6 +134,7 @@ func (r *RoomserverInternalAPI) SetFederationAPI(fsAPI fsAPI.RoomserverFederatio
|
|||
DB: r.DB,
|
||||
Cfg: &r.Cfg.RoomServer,
|
||||
FSAPI: r.fsAPI,
|
||||
RSAPI: r,
|
||||
Inputer: r.Inputer,
|
||||
}
|
||||
r.Joiner = &perform.Joiner{
|
||||
|
|
@ -192,6 +196,11 @@ func (r *RoomserverInternalAPI) SetFederationAPI(fsAPI fsAPI.RoomserverFederatio
|
|||
Queryer: r.Queryer,
|
||||
Leaver: r.Leaver,
|
||||
}
|
||||
r.Creator = &perform.Creator{
|
||||
DB: r.DB,
|
||||
Cfg: &r.Cfg.RoomServer,
|
||||
RSAPI: r,
|
||||
}
|
||||
|
||||
if err := r.Inputer.Start(); err != nil {
|
||||
logrus.WithError(err).Panic("failed to start roomserver input API")
|
||||
|
|
@ -207,18 +216,35 @@ func (r *RoomserverInternalAPI) SetAppserviceAPI(asAPI asAPI.AppServiceInternalA
|
|||
r.asAPI = asAPI
|
||||
}
|
||||
|
||||
func (r *RoomserverInternalAPI) IsKnownRoom(ctx context.Context, roomID spec.RoomID) (bool, error) {
|
||||
return r.Inviter.IsKnownRoom(ctx, roomID)
|
||||
}
|
||||
|
||||
func (r *RoomserverInternalAPI) StateQuerier() gomatrixserverlib.StateQuerier {
|
||||
return r.Inviter.StateQuerier()
|
||||
}
|
||||
|
||||
func (r *RoomserverInternalAPI) HandleInvite(
|
||||
ctx context.Context, inviteEvent *types.HeaderedEvent,
|
||||
) error {
|
||||
outputEvents, err := r.Inviter.ProcessInviteMembership(ctx, inviteEvent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.OutputProducer.ProduceRoomEvents(inviteEvent.RoomID(), outputEvents)
|
||||
}
|
||||
|
||||
func (r *RoomserverInternalAPI) PerformCreateRoom(
|
||||
ctx context.Context, userID spec.UserID, roomID spec.RoomID, createRequest *api.PerformCreateRoomRequest,
|
||||
) (string, *util.JSONResponse) {
|
||||
return r.Creator.PerformCreateRoom(ctx, userID, roomID, createRequest)
|
||||
}
|
||||
|
||||
func (r *RoomserverInternalAPI) PerformInvite(
|
||||
ctx context.Context,
|
||||
req *api.PerformInviteRequest,
|
||||
) error {
|
||||
outputEvents, err := r.Inviter.PerformInvite(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(outputEvents) == 0 {
|
||||
return nil
|
||||
}
|
||||
return r.OutputProducer.ProduceRoomEvents(req.Event.RoomID(), outputEvents)
|
||||
return r.Inviter.PerformInvite(ctx, req)
|
||||
}
|
||||
|
||||
func (r *RoomserverInternalAPI) PerformLeave(
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ func CheckForSoftFail(
|
|||
)
|
||||
|
||||
// Load the actual auth events from the database.
|
||||
authEvents, err := loadAuthEvents(ctx, db, roomInfo, stateNeeded, authStateEntries)
|
||||
authEvents, err := loadAuthEvents(ctx, db, roomInfo.RoomVersion, stateNeeded, authStateEntries)
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("loadAuthEvents: %w", err)
|
||||
}
|
||||
|
|
@ -83,15 +83,14 @@ func CheckForSoftFail(
|
|||
return false, nil
|
||||
}
|
||||
|
||||
// CheckAuthEvents checks that the event passes authentication checks
|
||||
// Returns the numeric IDs for the auth events.
|
||||
func CheckAuthEvents(
|
||||
// GetAuthEvents returns the numeric IDs for the auth events.
|
||||
func GetAuthEvents(
|
||||
ctx context.Context,
|
||||
db storage.RoomDatabase,
|
||||
roomInfo *types.RoomInfo,
|
||||
event *types.HeaderedEvent,
|
||||
roomVersion gomatrixserverlib.RoomVersion,
|
||||
event gomatrixserverlib.PDU,
|
||||
authEventIDs []string,
|
||||
) ([]types.EventNID, error) {
|
||||
) (gomatrixserverlib.AuthEventProvider, error) {
|
||||
// Grab the numeric IDs for the supplied auth state events from the database.
|
||||
authStateEntries, err := db.StateEntriesForEventIDs(ctx, authEventIDs, true)
|
||||
if err != nil {
|
||||
|
|
@ -100,25 +99,14 @@ func CheckAuthEvents(
|
|||
authStateEntries = types.DeduplicateStateEntries(authStateEntries)
|
||||
|
||||
// Work out which of the state events we actually need.
|
||||
stateNeeded := gomatrixserverlib.StateNeededForAuth([]gomatrixserverlib.PDU{event.PDU})
|
||||
stateNeeded := gomatrixserverlib.StateNeededForAuth([]gomatrixserverlib.PDU{event})
|
||||
|
||||
// Load the actual auth events from the database.
|
||||
authEvents, err := loadAuthEvents(ctx, db, roomInfo, stateNeeded, authStateEntries)
|
||||
authEvents, err := loadAuthEvents(ctx, db, roomVersion, stateNeeded, authStateEntries)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loadAuthEvents: %w", err)
|
||||
}
|
||||
|
||||
// Check if the event is allowed.
|
||||
if err = gomatrixserverlib.Allowed(event.PDU, &authEvents); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return the numeric IDs for the auth events.
|
||||
result := make([]types.EventNID, len(authStateEntries))
|
||||
for i := range authStateEntries {
|
||||
result[i] = authStateEntries[i].EventNID
|
||||
}
|
||||
return result, nil
|
||||
return &authEvents, nil
|
||||
}
|
||||
|
||||
type authEvents struct {
|
||||
|
|
@ -196,7 +184,7 @@ func (ae *authEvents) lookupEvent(typeNID types.EventTypeNID, stateKey string) g
|
|||
func loadAuthEvents(
|
||||
ctx context.Context,
|
||||
db state.StateResolutionStorage,
|
||||
roomInfo *types.RoomInfo,
|
||||
roomVersion gomatrixserverlib.RoomVersion,
|
||||
needed gomatrixserverlib.StateNeeded,
|
||||
state []types.StateEntry,
|
||||
) (result authEvents, err error) {
|
||||
|
|
@ -220,11 +208,7 @@ func loadAuthEvents(
|
|||
}
|
||||
}
|
||||
|
||||
if roomInfo == nil {
|
||||
err = types.ErrorInvalidRoomInfo
|
||||
return
|
||||
}
|
||||
if result.events, err = db.Events(ctx, roomInfo.RoomVersion, eventNIDs); err != nil {
|
||||
if result.events, err = db.Events(ctx, roomVersion, eventNIDs); err != nil {
|
||||
return
|
||||
}
|
||||
roomID := ""
|
||||
|
|
|
|||
|
|
@ -872,7 +872,7 @@ func (r *Inputer) kickGuests(ctx context.Context, event gomatrixserverlib.PDU, r
|
|||
return err
|
||||
}
|
||||
|
||||
event, err := eventutil.BuildEvent(ctx, fledglingEvent, r.Cfg.Matrix, r.SigningIdentity, time.Now(), &eventsNeeded, latestRes)
|
||||
event, err := eventutil.BuildEvent(ctx, fledglingEvent, r.SigningIdentity, time.Now(), &eventsNeeded, latestRes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ func (r *Admin) PerformAdminEvacuateRoom(
|
|||
continue
|
||||
}
|
||||
|
||||
event, err = eventutil.BuildEvent(ctx, fledglingEvent, r.Cfg.Matrix, identity, time.Now(), &eventsNeeded, latestRes)
|
||||
event, err = eventutil.BuildEvent(ctx, fledglingEvent, identity, time.Now(), &eventsNeeded, latestRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -312,7 +312,7 @@ func (r *Admin) PerformAdminDownloadState(
|
|||
return err
|
||||
}
|
||||
|
||||
ev, err := eventutil.BuildEvent(ctx, proto, r.Cfg.Matrix, identity, time.Now(), &eventsNeeded, queryRes)
|
||||
ev, err := eventutil.BuildEvent(ctx, proto, identity, time.Now(), &eventsNeeded, queryRes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("eventutil.BuildEvent: %w", err)
|
||||
}
|
||||
|
|
|
|||
498
roomserver/internal/perform/perform_create_room.go
Normal file
498
roomserver/internal/perform/perform_create_room.go
Normal file
|
|
@ -0,0 +1,498 @@
|
|||
// Copyright 2023 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"
|
||||
"net/http"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/roomserver/storage"
|
||||
"github.com/matrix-org/dendrite/roomserver/types"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
const (
|
||||
historyVisibilityShared = "shared"
|
||||
)
|
||||
|
||||
type Creator struct {
|
||||
DB storage.Database
|
||||
Cfg *config.RoomServer
|
||||
RSAPI api.RoomserverInternalAPI
|
||||
}
|
||||
|
||||
// PerformCreateRoom handles all the steps necessary to create a new room.
|
||||
// nolint: gocyclo
|
||||
func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roomID spec.RoomID, createRequest *api.PerformCreateRoomRequest) (string, *util.JSONResponse) {
|
||||
verImpl, err := gomatrixserverlib.GetRoomVersion(createRequest.RoomVersion)
|
||||
if err != nil {
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.BadJSON("unknown room version"),
|
||||
}
|
||||
}
|
||||
|
||||
createContent := map[string]interface{}{}
|
||||
if len(createRequest.CreationContent) > 0 {
|
||||
if err = json.Unmarshal(createRequest.CreationContent, &createContent); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for creation_content failed")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.BadJSON("invalid create content"),
|
||||
}
|
||||
}
|
||||
}
|
||||
createContent["creator"] = userID.String()
|
||||
createContent["room_version"] = createRequest.RoomVersion
|
||||
powerLevelContent := eventutil.InitialPowerLevelsContent(userID.String())
|
||||
joinRuleContent := gomatrixserverlib.JoinRuleContent{
|
||||
JoinRule: spec.Invite,
|
||||
}
|
||||
historyVisibilityContent := gomatrixserverlib.HistoryVisibilityContent{
|
||||
HistoryVisibility: historyVisibilityShared,
|
||||
}
|
||||
|
||||
if createRequest.PowerLevelContentOverride != nil {
|
||||
// Merge powerLevelContentOverride fields by unmarshalling it atop the defaults
|
||||
err = json.Unmarshal(createRequest.PowerLevelContentOverride, &powerLevelContent)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for power_level_content_override failed")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.BadJSON("malformed power_level_content_override"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var guestsCanJoin bool
|
||||
switch createRequest.StatePreset {
|
||||
case spec.PresetPrivateChat:
|
||||
joinRuleContent.JoinRule = spec.Invite
|
||||
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
|
||||
guestsCanJoin = true
|
||||
case spec.PresetTrustedPrivateChat:
|
||||
joinRuleContent.JoinRule = spec.Invite
|
||||
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
|
||||
for _, invitee := range createRequest.InvitedUsers {
|
||||
powerLevelContent.Users[invitee] = 100
|
||||
}
|
||||
guestsCanJoin = true
|
||||
case spec.PresetPublicChat:
|
||||
joinRuleContent.JoinRule = spec.Public
|
||||
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
|
||||
}
|
||||
|
||||
createEvent := gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomCreate,
|
||||
Content: createContent,
|
||||
}
|
||||
powerLevelEvent := gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomPowerLevels,
|
||||
Content: powerLevelContent,
|
||||
}
|
||||
joinRuleEvent := gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomJoinRules,
|
||||
Content: joinRuleContent,
|
||||
}
|
||||
historyVisibilityEvent := gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomHistoryVisibility,
|
||||
Content: historyVisibilityContent,
|
||||
}
|
||||
membershipEvent := gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomMember,
|
||||
StateKey: userID.String(),
|
||||
Content: gomatrixserverlib.MemberContent{
|
||||
Membership: spec.Join,
|
||||
DisplayName: createRequest.UserDisplayName,
|
||||
AvatarURL: createRequest.UserAvatarURL,
|
||||
},
|
||||
}
|
||||
|
||||
var nameEvent *gomatrixserverlib.FledglingEvent
|
||||
var topicEvent *gomatrixserverlib.FledglingEvent
|
||||
var guestAccessEvent *gomatrixserverlib.FledglingEvent
|
||||
var aliasEvent *gomatrixserverlib.FledglingEvent
|
||||
|
||||
if createRequest.RoomName != "" {
|
||||
nameEvent = &gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomName,
|
||||
Content: eventutil.NameContent{
|
||||
Name: createRequest.RoomName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if createRequest.Topic != "" {
|
||||
topicEvent = &gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomTopic,
|
||||
Content: eventutil.TopicContent{
|
||||
Topic: createRequest.Topic,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if guestsCanJoin {
|
||||
guestAccessEvent = &gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomGuestAccess,
|
||||
Content: eventutil.GuestAccessContent{
|
||||
GuestAccess: "can_join",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var roomAlias string
|
||||
if createRequest.RoomAliasName != "" {
|
||||
roomAlias = fmt.Sprintf("#%s:%s", createRequest.RoomAliasName, userID.Domain())
|
||||
// check it's free
|
||||
// TODO: This races but is better than nothing
|
||||
hasAliasReq := api.GetRoomIDForAliasRequest{
|
||||
Alias: roomAlias,
|
||||
IncludeAppservices: false,
|
||||
}
|
||||
|
||||
var aliasResp api.GetRoomIDForAliasResponse
|
||||
err = c.RSAPI.GetRoomIDForAlias(ctx, &hasAliasReq, &aliasResp)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("aliasAPI.GetRoomIDForAlias failed")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
if aliasResp.RoomID != "" {
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.RoomInUse("Room ID already exists."),
|
||||
}
|
||||
}
|
||||
|
||||
aliasEvent = &gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomCanonicalAlias,
|
||||
Content: eventutil.CanonicalAlias{
|
||||
Alias: roomAlias,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var initialStateEvents []gomatrixserverlib.FledglingEvent
|
||||
for i := range createRequest.InitialState {
|
||||
if createRequest.InitialState[i].StateKey != "" {
|
||||
initialStateEvents = append(initialStateEvents, createRequest.InitialState[i])
|
||||
continue
|
||||
}
|
||||
|
||||
switch createRequest.InitialState[i].Type {
|
||||
case spec.MRoomCreate:
|
||||
continue
|
||||
|
||||
case spec.MRoomPowerLevels:
|
||||
powerLevelEvent = createRequest.InitialState[i]
|
||||
|
||||
case spec.MRoomJoinRules:
|
||||
joinRuleEvent = createRequest.InitialState[i]
|
||||
|
||||
case spec.MRoomHistoryVisibility:
|
||||
historyVisibilityEvent = createRequest.InitialState[i]
|
||||
|
||||
case spec.MRoomGuestAccess:
|
||||
guestAccessEvent = &createRequest.InitialState[i]
|
||||
|
||||
case spec.MRoomName:
|
||||
nameEvent = &createRequest.InitialState[i]
|
||||
|
||||
case spec.MRoomTopic:
|
||||
topicEvent = &createRequest.InitialState[i]
|
||||
|
||||
default:
|
||||
initialStateEvents = append(initialStateEvents, createRequest.InitialState[i])
|
||||
}
|
||||
}
|
||||
|
||||
// send events into the room in order of:
|
||||
// 1- m.room.create
|
||||
// 2- room creator join member
|
||||
// 3- m.room.power_levels
|
||||
// 4- m.room.join_rules
|
||||
// 5- m.room.history_visibility
|
||||
// 6- m.room.canonical_alias (opt)
|
||||
// 7- m.room.guest_access (opt)
|
||||
// 8- other initial state items
|
||||
// 9- m.room.name (opt)
|
||||
// 10- m.room.topic (opt)
|
||||
// 11- invite events (opt) - with is_direct flag if applicable TODO
|
||||
// 12- 3pid invite events (opt) TODO
|
||||
// This differs from Synapse slightly. Synapse would vary the ordering of 3-7
|
||||
// depending on if those events were in "initial_state" or not. This made it
|
||||
// harder to reason about, hence sticking to a strict static ordering.
|
||||
// TODO: Synapse has txn/token ID on each event. Do we need to do this here?
|
||||
eventsToMake := []gomatrixserverlib.FledglingEvent{
|
||||
createEvent, membershipEvent, powerLevelEvent, joinRuleEvent, historyVisibilityEvent,
|
||||
}
|
||||
if guestAccessEvent != nil {
|
||||
eventsToMake = append(eventsToMake, *guestAccessEvent)
|
||||
}
|
||||
eventsToMake = append(eventsToMake, initialStateEvents...)
|
||||
if nameEvent != nil {
|
||||
eventsToMake = append(eventsToMake, *nameEvent)
|
||||
}
|
||||
if topicEvent != nil {
|
||||
eventsToMake = append(eventsToMake, *topicEvent)
|
||||
}
|
||||
if aliasEvent != nil {
|
||||
// TODO: bit of a chicken and egg problem here as the alias doesn't exist and cannot until we have made the room.
|
||||
// This means we might fail creating the alias but say the canonical alias is something that doesn't exist.
|
||||
eventsToMake = append(eventsToMake, *aliasEvent)
|
||||
}
|
||||
|
||||
// TODO: invite events
|
||||
// TODO: 3pid invite events
|
||||
|
||||
var builtEvents []*types.HeaderedEvent
|
||||
authEvents := gomatrixserverlib.NewAuthEvents(nil)
|
||||
for i, e := range eventsToMake {
|
||||
depth := i + 1 // depth starts at 1
|
||||
|
||||
builder := verImpl.NewEventBuilderFromProtoEvent(&gomatrixserverlib.ProtoEvent{
|
||||
Sender: userID.String(),
|
||||
RoomID: roomID.String(),
|
||||
Type: e.Type,
|
||||
StateKey: &e.StateKey,
|
||||
Depth: int64(depth),
|
||||
})
|
||||
err = builder.SetContent(e.Content)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("builder.SetContent failed")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
if i > 0 {
|
||||
builder.PrevEvents = []string{builtEvents[i-1].EventID()}
|
||||
}
|
||||
var ev gomatrixserverlib.PDU
|
||||
if err = builder.AddAuthEvents(&authEvents); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("AddAuthEvents failed")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
ev, err = builder.Build(createRequest.EventTime, userID.Domain(), createRequest.KeyID, createRequest.PrivateKey)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("buildEvent failed")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
if err = gomatrixserverlib.Allowed(ev, &authEvents); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.Allowed failed")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
// Add the event to the list of auth events
|
||||
builtEvents = append(builtEvents, &types.HeaderedEvent{PDU: ev})
|
||||
err = authEvents.AddEvent(ev)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("authEvents.AddEvent failed")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputs := make([]api.InputRoomEvent, 0, len(builtEvents))
|
||||
for _, event := range builtEvents {
|
||||
inputs = append(inputs, api.InputRoomEvent{
|
||||
Kind: api.KindNew,
|
||||
Event: event,
|
||||
Origin: userID.Domain(),
|
||||
SendAsServer: api.DoNotSendToOtherServers,
|
||||
})
|
||||
}
|
||||
if err = api.SendInputRoomEvents(ctx, c.RSAPI, userID.Domain(), inputs, false); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("roomserverAPI.SendInputRoomEvents failed")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(#269): Reserve room alias while we create the room. This stops us
|
||||
// from creating the room but still failing due to the alias having already
|
||||
// been taken.
|
||||
if roomAlias != "" {
|
||||
aliasReq := api.SetRoomAliasRequest{
|
||||
Alias: roomAlias,
|
||||
RoomID: roomID.String(),
|
||||
UserID: userID.String(),
|
||||
}
|
||||
|
||||
var aliasResp api.SetRoomAliasResponse
|
||||
err = c.RSAPI.SetRoomAlias(ctx, &aliasReq, &aliasResp)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("aliasAPI.SetRoomAlias failed")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
if aliasResp.AliasExists {
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.RoomInUse("Room alias already exists."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a direct message then we should invite the participants.
|
||||
if len(createRequest.InvitedUsers) > 0 {
|
||||
// Build some stripped state for the invite.
|
||||
var globalStrippedState []gomatrixserverlib.InviteStrippedState
|
||||
for _, event := range builtEvents {
|
||||
// Chosen events from the spec:
|
||||
// https://spec.matrix.org/v1.3/client-server-api/#stripped-state
|
||||
switch event.Type() {
|
||||
case spec.MRoomCreate:
|
||||
fallthrough
|
||||
case spec.MRoomName:
|
||||
fallthrough
|
||||
case spec.MRoomAvatar:
|
||||
fallthrough
|
||||
case spec.MRoomTopic:
|
||||
fallthrough
|
||||
case spec.MRoomCanonicalAlias:
|
||||
fallthrough
|
||||
case spec.MRoomEncryption:
|
||||
fallthrough
|
||||
case spec.MRoomMember:
|
||||
fallthrough
|
||||
case spec.MRoomJoinRules:
|
||||
ev := event.PDU
|
||||
globalStrippedState = append(
|
||||
globalStrippedState,
|
||||
gomatrixserverlib.NewInviteStrippedState(ev),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Process the invites.
|
||||
var inviteEvent *types.HeaderedEvent
|
||||
for _, invitee := range createRequest.InvitedUsers {
|
||||
proto := gomatrixserverlib.ProtoEvent{
|
||||
Sender: userID.String(),
|
||||
RoomID: roomID.String(),
|
||||
Type: "m.room.member",
|
||||
StateKey: &invitee,
|
||||
}
|
||||
|
||||
content := gomatrixserverlib.MemberContent{
|
||||
Membership: spec.Invite,
|
||||
DisplayName: createRequest.UserDisplayName,
|
||||
AvatarURL: createRequest.UserAvatarURL,
|
||||
Reason: "",
|
||||
IsDirect: createRequest.IsDirect,
|
||||
}
|
||||
|
||||
if err = proto.SetContent(content); err != nil {
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
// Build the invite event.
|
||||
identity := &fclient.SigningIdentity{
|
||||
ServerName: userID.Domain(),
|
||||
KeyID: createRequest.KeyID,
|
||||
PrivateKey: createRequest.PrivateKey,
|
||||
}
|
||||
inviteEvent, err = eventutil.QueryAndBuildEvent(ctx, &proto, identity, createRequest.EventTime, c.RSAPI, nil)
|
||||
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("buildMembershipEvent failed")
|
||||
continue
|
||||
}
|
||||
inviteStrippedState := append(
|
||||
globalStrippedState,
|
||||
gomatrixserverlib.NewInviteStrippedState(inviteEvent.PDU),
|
||||
)
|
||||
// Send the invite event to the roomserver.
|
||||
event := inviteEvent
|
||||
err = c.RSAPI.PerformInvite(ctx, &api.PerformInviteRequest{
|
||||
Event: event,
|
||||
InviteRoomState: inviteStrippedState,
|
||||
RoomVersion: event.Version(),
|
||||
SendAsServer: string(userID.Domain()),
|
||||
})
|
||||
switch e := err.(type) {
|
||||
case api.ErrInvalidID:
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.Unknown(e.Error()),
|
||||
}
|
||||
case api.ErrNotAllowed:
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: spec.Forbidden(e.Error()),
|
||||
}
|
||||
case nil:
|
||||
default:
|
||||
util.GetLogger(ctx).WithError(err).Error("PerformInvite failed")
|
||||
sentry.CaptureException(err)
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if createRequest.Visibility == spec.Public {
|
||||
// expose this room in the published room list
|
||||
if err = c.RSAPI.PerformPublish(ctx, &api.PerformPublishRequest{
|
||||
RoomID: roomID.String(),
|
||||
Visibility: spec.Public,
|
||||
}); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("failed to publish room")
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: visibility/presets/raw initial state
|
||||
// TODO: Create room alias association
|
||||
// Make sure this doesn't fall into an application service's namespace though!
|
||||
|
||||
return roomAlias, nil
|
||||
}
|
||||
|
|
@ -28,186 +28,149 @@ import (
|
|||
"github.com/matrix-org/dendrite/roomserver/types"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type QueryState struct {
|
||||
storage.Database
|
||||
}
|
||||
|
||||
func (q *QueryState) GetAuthEvents(ctx context.Context, event gomatrixserverlib.PDU) (gomatrixserverlib.AuthEventProvider, error) {
|
||||
return helpers.GetAuthEvents(ctx, q.Database, event.Version(), event, event.AuthEventIDs())
|
||||
}
|
||||
|
||||
func (q *QueryState) GetState(ctx context.Context, roomID spec.RoomID, stateWanted []gomatrixserverlib.StateKeyTuple) ([]gomatrixserverlib.PDU, error) {
|
||||
info, err := q.Database.RoomInfo(ctx, roomID.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load RoomInfo: %w", err)
|
||||
}
|
||||
if info != nil {
|
||||
roomState := state.NewStateResolution(q.Database, info)
|
||||
stateEntries, err := roomState.LoadStateAtSnapshotForStringTuples(
|
||||
ctx, info.StateSnapshotNID(), stateWanted,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
stateNIDs := []types.EventNID{}
|
||||
for _, stateNID := range stateEntries {
|
||||
stateNIDs = append(stateNIDs, stateNID.EventNID)
|
||||
}
|
||||
stateEvents, err := q.Database.Events(ctx, info.RoomVersion, stateNIDs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to obtain required events: %w", err)
|
||||
}
|
||||
|
||||
events := []gomatrixserverlib.PDU{}
|
||||
for _, event := range stateEvents {
|
||||
events = append(events, event.PDU)
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type Inviter struct {
|
||||
DB storage.Database
|
||||
Cfg *config.RoomServer
|
||||
FSAPI federationAPI.RoomserverFederationAPI
|
||||
RSAPI api.RoomserverInternalAPI
|
||||
Inputer *input.Inputer
|
||||
}
|
||||
|
||||
func (r *Inviter) IsKnownRoom(ctx context.Context, roomID spec.RoomID) (bool, error) {
|
||||
info, err := r.DB.RoomInfo(ctx, roomID.String())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to load RoomInfo: %w", err)
|
||||
}
|
||||
return (info != nil && !info.IsStub()), nil
|
||||
}
|
||||
|
||||
func (r *Inviter) StateQuerier() gomatrixserverlib.StateQuerier {
|
||||
return &QueryState{Database: r.DB}
|
||||
}
|
||||
|
||||
func (r *Inviter) ProcessInviteMembership(
|
||||
ctx context.Context, inviteEvent *types.HeaderedEvent,
|
||||
) ([]api.OutputEvent, error) {
|
||||
var outputUpdates []api.OutputEvent
|
||||
var updater *shared.MembershipUpdater
|
||||
_, domain, err := gomatrixserverlib.SplitID('@', *inviteEvent.StateKey())
|
||||
if err != nil {
|
||||
return nil, api.ErrInvalidID{Err: fmt.Errorf("the user ID %s is invalid", *inviteEvent.StateKey())}
|
||||
}
|
||||
isTargetLocal := r.Cfg.Matrix.IsLocalServerName(domain)
|
||||
if updater, err = r.DB.MembershipUpdater(ctx, inviteEvent.RoomID(), *inviteEvent.StateKey(), isTargetLocal, inviteEvent.Version()); err != nil {
|
||||
return nil, fmt.Errorf("r.DB.MembershipUpdater: %w", err)
|
||||
}
|
||||
outputUpdates, err = helpers.UpdateToInviteMembership(updater, &types.Event{
|
||||
EventNID: 0,
|
||||
PDU: inviteEvent.PDU,
|
||||
}, outputUpdates, inviteEvent.Version())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("updateToInviteMembership: %w", err)
|
||||
}
|
||||
if err = updater.Commit(); err != nil {
|
||||
return nil, fmt.Errorf("updater.Commit: %w", err)
|
||||
}
|
||||
return outputUpdates, nil
|
||||
}
|
||||
|
||||
// nolint:gocyclo
|
||||
func (r *Inviter) PerformInvite(
|
||||
ctx context.Context,
|
||||
req *api.PerformInviteRequest,
|
||||
) ([]api.OutputEvent, error) {
|
||||
var outputUpdates []api.OutputEvent
|
||||
) error {
|
||||
event := req.Event
|
||||
|
||||
sender, err := spec.NewUserID(event.Sender(), true)
|
||||
if err != nil {
|
||||
return spec.InvalidParam("The user ID is invalid")
|
||||
}
|
||||
if !r.Cfg.Matrix.IsLocalServerName(sender.Domain()) {
|
||||
return api.ErrInvalidID{Err: fmt.Errorf("the invite must be from a local user")}
|
||||
}
|
||||
|
||||
if event.StateKey() == nil {
|
||||
return nil, fmt.Errorf("invite must be a state event")
|
||||
return fmt.Errorf("invite must be a state event")
|
||||
}
|
||||
_, senderDomain, err := gomatrixserverlib.SplitID('@', event.Sender())
|
||||
invitedUser, err := spec.NewUserID(*event.StateKey(), true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sender %q is invalid", event.Sender())
|
||||
return spec.InvalidParam("The user ID is invalid")
|
||||
}
|
||||
isTargetLocal := r.Cfg.Matrix.IsLocalServerName(invitedUser.Domain())
|
||||
|
||||
roomID := event.RoomID()
|
||||
targetUserID := *event.StateKey()
|
||||
info, err := r.DB.RoomInfo(ctx, roomID)
|
||||
validRoomID, err := spec.NewRoomID(event.RoomID())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load RoomInfo: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, domain, err := gomatrixserverlib.SplitID('@', targetUserID)
|
||||
input := gomatrixserverlib.PerformInviteInput{
|
||||
RoomID: *validRoomID,
|
||||
InviteEvent: event.PDU,
|
||||
InvitedUser: *invitedUser,
|
||||
IsTargetLocal: isTargetLocal,
|
||||
StrippedState: req.InviteRoomState,
|
||||
MembershipQuerier: &api.MembershipQuerier{Roomserver: r.RSAPI},
|
||||
StateQuerier: &QueryState{r.DB},
|
||||
}
|
||||
inviteEvent, err := gomatrixserverlib.PerformInvite(ctx, input, r.FSAPI)
|
||||
if err != nil {
|
||||
return nil, api.ErrInvalidID{Err: fmt.Errorf("the user ID %s is invalid", targetUserID)}
|
||||
}
|
||||
isTargetLocal := r.Cfg.Matrix.IsLocalServerName(domain)
|
||||
isOriginLocal := r.Cfg.Matrix.IsLocalServerName(senderDomain)
|
||||
if !isOriginLocal && !isTargetLocal {
|
||||
return nil, api.ErrInvalidID{Err: fmt.Errorf("the invite must be either from or to a local user")}
|
||||
switch e := err.(type) {
|
||||
case spec.MatrixError:
|
||||
if e.ErrCode == spec.ErrorForbidden {
|
||||
return api.ErrNotAllowed{Err: fmt.Errorf("%s", e.Err)}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
logger := util.GetLogger(ctx).WithFields(map[string]interface{}{
|
||||
"inviter": event.Sender(),
|
||||
"invitee": *event.StateKey(),
|
||||
"room_id": roomID,
|
||||
"event_id": event.EventID(),
|
||||
})
|
||||
logger.WithFields(log.Fields{
|
||||
"room_version": req.RoomVersion,
|
||||
"room_info_exists": info != nil,
|
||||
"target_local": isTargetLocal,
|
||||
"origin_local": isOriginLocal,
|
||||
}).Debug("processing invite event")
|
||||
|
||||
inviteState := req.InviteRoomState
|
||||
if len(inviteState) == 0 && info != nil {
|
||||
var is []fclient.InviteV2StrippedState
|
||||
if is, err = buildInviteStrippedState(ctx, r.DB, info, req); err == nil {
|
||||
inviteState = is
|
||||
}
|
||||
}
|
||||
if len(inviteState) == 0 {
|
||||
if err = event.SetUnsignedField("invite_room_state", struct{}{}); err != nil {
|
||||
return nil, fmt.Errorf("event.SetUnsignedField: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err = event.SetUnsignedField("invite_room_state", inviteState); err != nil {
|
||||
return nil, fmt.Errorf("event.SetUnsignedField: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
updateMembershipTableManually := func() ([]api.OutputEvent, error) {
|
||||
var updater *shared.MembershipUpdater
|
||||
if updater, err = r.DB.MembershipUpdater(ctx, roomID, targetUserID, isTargetLocal, req.RoomVersion); err != nil {
|
||||
return nil, fmt.Errorf("r.DB.MembershipUpdater: %w", err)
|
||||
}
|
||||
outputUpdates, err = helpers.UpdateToInviteMembership(updater, &types.Event{
|
||||
EventNID: 0,
|
||||
PDU: event.PDU,
|
||||
}, outputUpdates, req.Event.Version())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("updateToInviteMembership: %w", err)
|
||||
}
|
||||
if err = updater.Commit(); err != nil {
|
||||
return nil, fmt.Errorf("updater.Commit: %w", err)
|
||||
}
|
||||
logger.Debugf("updated membership to invite and sending invite OutputEvent")
|
||||
return outputUpdates, nil
|
||||
}
|
||||
|
||||
if (info == nil || info.IsStub()) && !isOriginLocal && isTargetLocal {
|
||||
// The invite came in over federation for a room that we don't know about
|
||||
// yet. We need to handle this a bit differently to most invites because
|
||||
// we don't know the room state, therefore the roomserver can't process
|
||||
// an input event. Instead we will update the membership table with the
|
||||
// new invite and generate an output event.
|
||||
return updateMembershipTableManually()
|
||||
}
|
||||
|
||||
var isAlreadyJoined bool
|
||||
if info != nil {
|
||||
_, isAlreadyJoined, _, err = r.DB.GetMembership(ctx, info.RoomNID, *event.StateKey())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("r.DB.GetMembership: %w", err)
|
||||
}
|
||||
}
|
||||
if isAlreadyJoined {
|
||||
// If the user is joined to the room then that takes precedence over this
|
||||
// invite event. It makes little sense to move a user that is already
|
||||
// joined to the room into the invite state.
|
||||
// This could plausibly happen if an invite request raced with a join
|
||||
// request for a user. For example if a user was invited to a public
|
||||
// room and they joined the room at the same time as the invite was sent.
|
||||
// The other way this could plausibly happen is if an invite raced with
|
||||
// a kick. For example if a user was kicked from a room in error and in
|
||||
// response someone else in the room re-invited them then it is possible
|
||||
// for the invite request to race with the leave event so that the
|
||||
// target receives invite before it learns that it has been kicked.
|
||||
// There are a few ways this could be plausibly handled in the roomserver.
|
||||
// 1) Store the invite, but mark it as retired. That will result in the
|
||||
// permanent rejection of that invite event. So even if the target
|
||||
// user leaves the room and the invite is retransmitted it will be
|
||||
// ignored. However a new invite with a new event ID would still be
|
||||
// accepted.
|
||||
// 2) Silently discard the invite event. This means that if the event
|
||||
// was retransmitted at a later date after the target user had left
|
||||
// the room we would accept the invite. However since we hadn't told
|
||||
// the sending server that the invite had been discarded it would
|
||||
// have no reason to attempt to retry.
|
||||
// 3) Signal the sending server that the user is already joined to the
|
||||
// room.
|
||||
// For now we will implement option 2. Since in the abesence of a retry
|
||||
// mechanism it will be equivalent to option 1, and we don't have a
|
||||
// signalling mechanism to implement option 3.
|
||||
logger.Debugf("user already joined")
|
||||
return nil, api.ErrNotAllowed{Err: fmt.Errorf("user is already joined to room")}
|
||||
}
|
||||
|
||||
// If the invite originated remotely then we can't send an
|
||||
// InputRoomEvent for the invite as it will never pass auth checks
|
||||
// due to lacking room state, but we still need to tell the client
|
||||
// about the invite so we can accept it, hence we return an output
|
||||
// event to send to the Sync API.
|
||||
if !isOriginLocal {
|
||||
return updateMembershipTableManually()
|
||||
}
|
||||
|
||||
// The invite originated locally. Therefore we have a responsibility to
|
||||
// try and see if the user is allowed to make this invite. We can't do
|
||||
// this for invites coming in over federation - we have to take those on
|
||||
// trust.
|
||||
_, err = helpers.CheckAuthEvents(ctx, r.DB, info, event, event.AuthEventIDs())
|
||||
if err != nil {
|
||||
logger.WithError(err).WithField("event_id", event.EventID()).WithField("auth_event_ids", event.AuthEventIDs()).Error(
|
||||
"processInviteEvent.checkAuthEvents failed for event",
|
||||
)
|
||||
return nil, api.ErrNotAllowed{Err: err}
|
||||
}
|
||||
|
||||
// If the invite originated from us and the target isn't local then we
|
||||
// should try and send the invite over federation first. It might be
|
||||
// that the remote user doesn't exist, in which case we can give up
|
||||
// processing here.
|
||||
if req.SendAsServer != api.DoNotSendToOtherServers && !isTargetLocal {
|
||||
fsReq := &federationAPI.PerformInviteRequest{
|
||||
RoomVersion: req.RoomVersion,
|
||||
Event: event,
|
||||
InviteRoomState: inviteState,
|
||||
}
|
||||
fsRes := &federationAPI.PerformInviteResponse{}
|
||||
if err = r.FSAPI.PerformInvite(ctx, fsReq, fsRes); err != nil {
|
||||
logger.WithError(err).WithField("event_id", event.EventID()).Error("r.FSAPI.PerformInvite failed")
|
||||
return nil, api.ErrNotAllowed{Err: err}
|
||||
}
|
||||
event = fsRes.Event
|
||||
logger.Debugf("Federated PerformInvite success with event ID %s", event.EventID())
|
||||
// Use the returned event if there was one (due to federation), otherwise
|
||||
// send the original invite event to the roomserver.
|
||||
if inviteEvent == nil {
|
||||
inviteEvent = event
|
||||
}
|
||||
|
||||
// Send the invite event to the roomserver input stream. This will
|
||||
|
|
@ -219,67 +182,18 @@ func (r *Inviter) PerformInvite(
|
|||
InputRoomEvents: []api.InputRoomEvent{
|
||||
{
|
||||
Kind: api.KindNew,
|
||||
Event: event,
|
||||
Origin: senderDomain,
|
||||
Event: &types.HeaderedEvent{PDU: inviteEvent},
|
||||
Origin: sender.Domain(),
|
||||
SendAsServer: req.SendAsServer,
|
||||
},
|
||||
},
|
||||
}
|
||||
inputRes := &api.InputRoomEventsResponse{}
|
||||
r.Inputer.InputRoomEvents(context.Background(), inputReq, inputRes)
|
||||
if err = inputRes.Err(); err != nil {
|
||||
logger.WithError(err).WithField("event_id", event.EventID()).Error("r.InputRoomEvents failed")
|
||||
return nil, api.ErrNotAllowed{Err: err}
|
||||
if err := inputRes.Err(); err != nil {
|
||||
util.GetLogger(ctx).WithField("event_id", event.EventID()).Error("r.InputRoomEvents failed")
|
||||
return api.ErrNotAllowed{Err: err}
|
||||
}
|
||||
|
||||
// Don't notify the sync api of this event in the same way as a federated invite so the invitee
|
||||
// gets the invite, as the roomserver will do this when it processes the m.room.member invite.
|
||||
return outputUpdates, nil
|
||||
}
|
||||
|
||||
func buildInviteStrippedState(
|
||||
ctx context.Context,
|
||||
db storage.Database,
|
||||
info *types.RoomInfo,
|
||||
input *api.PerformInviteRequest,
|
||||
) ([]fclient.InviteV2StrippedState, error) {
|
||||
stateWanted := []gomatrixserverlib.StateKeyTuple{}
|
||||
// "If they are set on the room, at least the state for m.room.avatar, m.room.canonical_alias, m.room.join_rules, and m.room.name SHOULD be included."
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-member
|
||||
for _, t := range []string{
|
||||
spec.MRoomName, spec.MRoomCanonicalAlias,
|
||||
spec.MRoomJoinRules, spec.MRoomAvatar,
|
||||
spec.MRoomEncryption, spec.MRoomCreate,
|
||||
} {
|
||||
stateWanted = append(stateWanted, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: t,
|
||||
StateKey: "",
|
||||
})
|
||||
}
|
||||
roomState := state.NewStateResolution(db, info)
|
||||
stateEntries, err := roomState.LoadStateAtSnapshotForStringTuples(
|
||||
ctx, info.StateSnapshotNID(), stateWanted,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stateNIDs := []types.EventNID{}
|
||||
for _, stateNID := range stateEntries {
|
||||
stateNIDs = append(stateNIDs, stateNID.EventNID)
|
||||
}
|
||||
if info == nil {
|
||||
return nil, types.ErrorInvalidRoomInfo
|
||||
}
|
||||
stateEvents, err := db.Events(ctx, info.RoomVersion, stateNIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inviteState := []fclient.InviteV2StrippedState{
|
||||
fclient.NewInviteV2StrippedState(input.Event.PDU),
|
||||
}
|
||||
stateEvents = append(stateEvents, types.Event{PDU: input.Event.PDU})
|
||||
for _, event := range stateEvents {
|
||||
inviteState = append(inviteState, fclient.NewInviteV2StrippedState(event.PDU))
|
||||
}
|
||||
return inviteState, nil
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -284,7 +284,7 @@ func (r *Joiner) performJoinRoomByID(
|
|||
if err != nil {
|
||||
return "", "", fmt.Errorf("error joining local room: %q", err)
|
||||
}
|
||||
event, err := eventutil.QueryAndBuildEvent(ctx, &proto, r.Cfg.Matrix, identity, time.Now(), r.RSAPI, &buildRes)
|
||||
event, err := eventutil.QueryAndBuildEvent(ctx, &proto, identity, time.Now(), r.RSAPI, &buildRes)
|
||||
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ func (r *Leaver) performLeaveRoomByID(
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("SigningIdentityFor: %w", err)
|
||||
}
|
||||
event, err := eventutil.QueryAndBuildEvent(ctx, &proto, r.Cfg.Matrix, identity, time.Now(), r.RSAPI, &buildRes)
|
||||
event, err := eventutil.QueryAndBuildEvent(ctx, &proto, identity, time.Now(), r.RSAPI, &buildRes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("eventutil.QueryAndBuildEvent: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ func (r *Peeker) performPeekRoomByID(
|
|||
content := map[string]string{}
|
||||
if err = json.Unmarshal(ev.Content(), &content); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for history visibility failed")
|
||||
return
|
||||
return "", err
|
||||
}
|
||||
if visibility, ok := content["history_visibility"]; ok {
|
||||
worldReadable = visibility == "world_readable"
|
||||
|
|
@ -185,7 +185,7 @@ func (r *Peeker) performPeekRoomByID(
|
|||
},
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
return "", err
|
||||
}
|
||||
|
||||
// By this point, if req.RoomIDOrAlias contained an alias, then
|
||||
|
|
|
|||
|
|
@ -35,13 +35,6 @@ type Upgrader struct {
|
|||
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,
|
||||
|
|
@ -154,7 +147,7 @@ func (r *Upgrader) restrictOldRoomPowerLevels(ctx context.Context, evTime time.T
|
|||
restrictedPowerLevelContent.EventsDefault = restrictedDefaultPowerLevel
|
||||
restrictedPowerLevelContent.Invite = restrictedDefaultPowerLevel
|
||||
|
||||
restrictedPowerLevelsHeadered, resErr := r.makeHeaderedEvent(ctx, evTime, userID, roomID, fledglingEvent{
|
||||
restrictedPowerLevelsHeadered, resErr := r.makeHeaderedEvent(ctx, evTime, userID, roomID, gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomPowerLevels,
|
||||
StateKey: "",
|
||||
Content: restrictedPowerLevelContent,
|
||||
|
|
@ -216,7 +209,7 @@ func (r *Upgrader) clearOldCanonicalAliasEvent(ctx context.Context, oldRoom *api
|
|||
}
|
||||
}
|
||||
|
||||
emptyCanonicalAliasEvent, resErr := r.makeHeaderedEvent(ctx, evTime, userID, roomID, fledglingEvent{
|
||||
emptyCanonicalAliasEvent, resErr := r.makeHeaderedEvent(ctx, evTime, userID, roomID, gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomCanonicalAlias,
|
||||
Content: map[string]interface{}{},
|
||||
})
|
||||
|
|
@ -298,7 +291,7 @@ func (r *Upgrader) userIsAuthorized(ctx context.Context, userID, roomID string,
|
|||
}
|
||||
|
||||
// nolint:gocyclo
|
||||
func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.QueryLatestEventsAndStateResponse, userID, roomID string, newVersion gomatrixserverlib.RoomVersion, tombstoneEvent *types.HeaderedEvent) ([]fledglingEvent, error) {
|
||||
func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.QueryLatestEventsAndStateResponse, userID, roomID string, newVersion gomatrixserverlib.RoomVersion, tombstoneEvent *types.HeaderedEvent) ([]gomatrixserverlib.FledglingEvent, error) {
|
||||
state := make(map[gomatrixserverlib.StateKeyTuple]*types.HeaderedEvent, len(oldRoom.StateEvents))
|
||||
for _, event := range oldRoom.StateEvents {
|
||||
if event.StateKey() == nil {
|
||||
|
|
@ -361,7 +354,7 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query
|
|||
EventID: tombstoneEvent.EventID(),
|
||||
RoomID: roomID,
|
||||
}
|
||||
newCreateEvent := fledglingEvent{
|
||||
newCreateEvent := gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomCreate,
|
||||
StateKey: "",
|
||||
Content: newCreateContent,
|
||||
|
|
@ -374,7 +367,7 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query
|
|||
newMembershipContent := map[string]interface{}{}
|
||||
_ = json.Unmarshal(oldMembershipEvent.Content(), &newMembershipContent)
|
||||
newMembershipContent["membership"] = spec.Join
|
||||
newMembershipEvent := fledglingEvent{
|
||||
newMembershipEvent := gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomMember,
|
||||
StateKey: userID,
|
||||
Content: newMembershipContent,
|
||||
|
|
@ -400,13 +393,13 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query
|
|||
"join_rule": spec.Invite, // sane default
|
||||
}
|
||||
_ = json.Unmarshal(oldJoinRulesEvent.Content(), &newJoinRulesContent)
|
||||
newJoinRulesEvent := fledglingEvent{
|
||||
newJoinRulesEvent := gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomJoinRules,
|
||||
StateKey: "",
|
||||
Content: newJoinRulesContent,
|
||||
}
|
||||
|
||||
eventsToMake := make([]fledglingEvent, 0, len(state))
|
||||
eventsToMake := make([]gomatrixserverlib.FledglingEvent, 0, len(state))
|
||||
eventsToMake = append(
|
||||
eventsToMake, newCreateEvent, newMembershipEvent,
|
||||
tempPowerLevelsEvent, newJoinRulesEvent,
|
||||
|
|
@ -415,7 +408,7 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query
|
|||
// 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: spec.MRoomGuestAccess, StateKey: ""}]; !ok {
|
||||
eventsToMake = append(eventsToMake, fledglingEvent{
|
||||
eventsToMake = append(eventsToMake, gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomGuestAccess,
|
||||
Content: map[string]string{
|
||||
"guest_access": "forbidden",
|
||||
|
|
@ -430,7 +423,7 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query
|
|||
// are already in `eventsToMake`.
|
||||
continue
|
||||
}
|
||||
newEvent := fledglingEvent{
|
||||
newEvent := gomatrixserverlib.FledglingEvent{
|
||||
Type: tuple.EventType,
|
||||
StateKey: tuple.StateKey,
|
||||
}
|
||||
|
|
@ -444,7 +437,7 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query
|
|||
// 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{
|
||||
eventsToMake = append(eventsToMake, gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomPowerLevels,
|
||||
Content: powerLevelContent,
|
||||
})
|
||||
|
|
@ -452,7 +445,7 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query
|
|||
return eventsToMake, nil
|
||||
}
|
||||
|
||||
func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, userID string, userDomain spec.ServerName, newRoomID string, newVersion gomatrixserverlib.RoomVersion, eventsToMake []fledglingEvent) error {
|
||||
func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, userID string, userDomain spec.ServerName, newRoomID string, newVersion gomatrixserverlib.RoomVersion, eventsToMake []gomatrixserverlib.FledglingEvent) error {
|
||||
var err error
|
||||
var builtEvents []*types.HeaderedEvent
|
||||
authEvents := gomatrixserverlib.NewAuthEvents(nil)
|
||||
|
|
@ -527,14 +520,14 @@ func (r *Upgrader) makeTombstoneEvent(
|
|||
"body": "This room has been replaced",
|
||||
"replacement_room": newRoomID,
|
||||
}
|
||||
event := fledglingEvent{
|
||||
event := gomatrixserverlib.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) (*types.HeaderedEvent, error) {
|
||||
func (r *Upgrader) makeHeaderedEvent(ctx context.Context, evTime time.Time, userID, roomID string, event gomatrixserverlib.FledglingEvent) (*types.HeaderedEvent, error) {
|
||||
proto := gomatrixserverlib.ProtoEvent{
|
||||
Sender: userID,
|
||||
RoomID: roomID,
|
||||
|
|
@ -555,7 +548,7 @@ func (r *Upgrader) makeHeaderedEvent(ctx context.Context, evTime time.Time, user
|
|||
return nil, fmt.Errorf("failed to get signing identity for %q: %w", senderDomain, err)
|
||||
}
|
||||
var queryRes api.QueryLatestEventsAndStateResponse
|
||||
headeredEvent, err := eventutil.QueryAndBuildEvent(ctx, &proto, r.Cfg.Matrix, identity, evTime, r.URSAPI, &queryRes)
|
||||
headeredEvent, err := eventutil.QueryAndBuildEvent(ctx, &proto, identity, evTime, r.URSAPI, &queryRes)
|
||||
switch e := err.(type) {
|
||||
case nil:
|
||||
case eventutil.ErrRoomNoExists:
|
||||
|
|
@ -581,7 +574,7 @@ func (r *Upgrader) makeHeaderedEvent(ctx context.Context, evTime time.Time, user
|
|||
return headeredEvent, nil
|
||||
}
|
||||
|
||||
func createTemporaryPowerLevels(powerLevelContent *gomatrixserverlib.PowerLevelContent, userID string) (fledglingEvent, bool) {
|
||||
func createTemporaryPowerLevels(powerLevelContent *gomatrixserverlib.PowerLevelContent, userID string) (gomatrixserverlib.FledglingEvent, bool) {
|
||||
// Work out what power level we need in order to be able to send events
|
||||
// of all types into the room.
|
||||
neededPowerLevel := powerLevelContent.StateDefault
|
||||
|
|
@ -612,7 +605,7 @@ func createTemporaryPowerLevels(powerLevelContent *gomatrixserverlib.PowerLevelC
|
|||
}
|
||||
|
||||
// Then return the temporary power levels event.
|
||||
return fledglingEvent{
|
||||
return gomatrixserverlib.FledglingEvent{
|
||||
Type: spec.MRoomPowerLevels,
|
||||
Content: tempPowerLevelContent,
|
||||
}, powerLevelsOverridden
|
||||
|
|
|
|||
|
|
@ -18,19 +18,14 @@ import (
|
|||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
func UpDropEventReferenceSHAEvents(ctx context.Context, tx *sql.Tx) error {
|
||||
var count int
|
||||
err := tx.QueryRowContext(ctx, `SELECT count(*) FROM roomserver_events GROUP BY event_id HAVING count(event_id) > 1`).
|
||||
Scan(&count)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return fmt.Errorf("failed to query duplicate event ids")
|
||||
}
|
||||
if count > 0 {
|
||||
return fmt.Errorf("unable to drop column, as there are duplicate event ids")
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, `ALTER TABLE roomserver_events DROP COLUMN IF EXISTS reference_sha256;`)
|
||||
_, err := tx.ExecContext(ctx, `ALTER TABLE roomserver_events DROP COLUMN IF EXISTS reference_sha256;`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute upgrade: %w", err)
|
||||
}
|
||||
|
|
@ -46,9 +41,80 @@ func UpDropEventReferenceSHAPrevEvents(ctx context.Context, tx *sql.Tx) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("failed to execute upgrade: %w", err)
|
||||
}
|
||||
|
||||
// figure out if there are duplicates
|
||||
dupeRows, err := tx.QueryContext(ctx, `SELECT previous_event_id FROM roomserver_previous_events GROUP BY previous_event_id HAVING count(previous_event_id) > 1`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to query duplicate event ids")
|
||||
}
|
||||
defer internal.CloseAndLogIfError(ctx, dupeRows, "failed to close rows")
|
||||
|
||||
var prevEvents []string
|
||||
var prevEventID string
|
||||
for dupeRows.Next() {
|
||||
if err = dupeRows.Scan(&prevEventID); err != nil {
|
||||
return err
|
||||
}
|
||||
prevEvents = append(prevEvents, prevEventID)
|
||||
}
|
||||
if dupeRows.Err() != nil {
|
||||
return dupeRows.Err()
|
||||
}
|
||||
|
||||
// if we found duplicates, check if we can combine them, e.g. they are in the same room
|
||||
for _, dupeID := range prevEvents {
|
||||
var dupeNIDsRows *sql.Rows
|
||||
dupeNIDsRows, err = tx.QueryContext(ctx, `SELECT event_nids FROM roomserver_previous_events WHERE previous_event_id = $1`, dupeID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to query duplicate event ids")
|
||||
}
|
||||
defer internal.CloseAndLogIfError(ctx, dupeNIDsRows, "failed to close rows")
|
||||
var dupeNIDs []int64
|
||||
for dupeNIDsRows.Next() {
|
||||
var nids pq.Int64Array
|
||||
if err = dupeNIDsRows.Scan(&nids); err != nil {
|
||||
return err
|
||||
}
|
||||
dupeNIDs = append(dupeNIDs, nids...)
|
||||
}
|
||||
|
||||
if dupeNIDsRows.Err() != nil {
|
||||
return dupeNIDsRows.Err()
|
||||
}
|
||||
// dedupe NIDs
|
||||
dupeNIDs = dupeNIDs[:util.SortAndUnique(nids(dupeNIDs))]
|
||||
// now that we have all NIDs, check which room they belong to
|
||||
var roomCount int
|
||||
err = tx.QueryRowContext(ctx, `SELECT count(distinct room_nid) FROM roomserver_events WHERE event_nid = ANY($1)`, pq.Array(dupeNIDs)).Scan(&roomCount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// if the events are from different rooms, that's bad and we can't continue
|
||||
if roomCount > 1 {
|
||||
return fmt.Errorf("detected events (%v) referenced for different rooms (%v)", dupeNIDs, roomCount)
|
||||
}
|
||||
// otherwise delete the dupes
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM roomserver_previous_events WHERE previous_event_id = $1", dupeID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to delete duplicates: %w", err)
|
||||
}
|
||||
|
||||
// insert combined values
|
||||
_, err = tx.ExecContext(ctx, "INSERT INTO roomserver_previous_events (previous_event_id, event_nids) VALUES ($1, $2)", dupeID, pq.Array(dupeNIDs))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to insert new event NIDs: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, `ALTER TABLE roomserver_previous_events ADD CONSTRAINT roomserver_previous_event_id_unique UNIQUE (previous_event_id);`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute upgrade: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type nids []int64
|
||||
|
||||
func (s nids) Len() int { return len(s) }
|
||||
func (s nids) Less(i, j int) bool { return s[i] < s[j] }
|
||||
func (s nids) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
package deltas
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/dendrite/test"
|
||||
"github.com/matrix-org/dendrite/test/testrig"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUpDropEventReferenceSHAPrevEvents(t *testing.T) {
|
||||
|
||||
cfg, ctx, close := testrig.CreateConfig(t, test.DBTypePostgres)
|
||||
defer close()
|
||||
|
||||
db, err := sqlutil.Open(&cfg.Global.DatabaseOptions, sqlutil.NewDummyWriter())
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, db)
|
||||
defer db.Close()
|
||||
|
||||
// create the table in the old layout
|
||||
_, err = db.ExecContext(ctx.Context(), `
|
||||
CREATE TABLE IF NOT EXISTS roomserver_previous_events (
|
||||
previous_event_id TEXT NOT NULL,
|
||||
event_nids BIGINT[] NOT NULL,
|
||||
previous_reference_sha256 BYTEA NOT NULL,
|
||||
CONSTRAINT roomserver_previous_event_id_unique UNIQUE (previous_event_id, previous_reference_sha256)
|
||||
);`)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// create the events table as well, slimmed down with one eventNID
|
||||
_, err = db.ExecContext(ctx.Context(), `
|
||||
CREATE SEQUENCE IF NOT EXISTS roomserver_event_nid_seq;
|
||||
CREATE TABLE IF NOT EXISTS roomserver_events (
|
||||
event_nid BIGINT PRIMARY KEY DEFAULT nextval('roomserver_event_nid_seq'),
|
||||
room_nid BIGINT NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO roomserver_events (event_nid, room_nid) VALUES (1, 1)
|
||||
`)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// insert duplicate prev events with different event_nids
|
||||
stmt, err := db.PrepareContext(ctx.Context(), `INSERT INTO roomserver_previous_events (previous_event_id, event_nids, previous_reference_sha256) VALUES ($1, $2, $3)`)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, stmt)
|
||||
_, err = stmt.ExecContext(ctx.Context(), "1", pq.Array([]int64{1, 2}), "a")
|
||||
assert.Nil(t, err)
|
||||
_, err = stmt.ExecContext(ctx.Context(), "1", pq.Array([]int64{1, 2, 3}), "b")
|
||||
assert.Nil(t, err)
|
||||
// execute the migration
|
||||
txn, err := db.Begin()
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, txn)
|
||||
defer txn.Rollback()
|
||||
err = UpDropEventReferenceSHAPrevEvents(ctx.Context(), txn)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
@ -18,6 +18,10 @@ import (
|
|||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
func UpDropEventReferenceSHA(ctx context.Context, tx *sql.Tx) error {
|
||||
|
|
@ -52,8 +56,72 @@ func UpDropEventReferenceSHAPrevEvents(ctx context.Context, tx *sql.Tx) error {
|
|||
return fmt.Errorf("tx.ExecContext: %w", err)
|
||||
}
|
||||
|
||||
// figure out if there are duplicates
|
||||
dupeRows, err := tx.QueryContext(ctx, `SELECT previous_event_id FROM _roomserver_previous_events GROUP BY previous_event_id HAVING count(previous_event_id) > 1`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to query duplicate event ids")
|
||||
}
|
||||
defer internal.CloseAndLogIfError(ctx, dupeRows, "failed to close rows")
|
||||
|
||||
var prevEvents []string
|
||||
var prevEventID string
|
||||
for dupeRows.Next() {
|
||||
if err = dupeRows.Scan(&prevEventID); err != nil {
|
||||
return err
|
||||
}
|
||||
prevEvents = append(prevEvents, prevEventID)
|
||||
}
|
||||
if dupeRows.Err() != nil {
|
||||
return dupeRows.Err()
|
||||
}
|
||||
|
||||
// if we found duplicates, check if we can combine them, e.g. they are in the same room
|
||||
for _, dupeID := range prevEvents {
|
||||
var dupeNIDsRows *sql.Rows
|
||||
dupeNIDsRows, err = tx.QueryContext(ctx, `SELECT event_nids FROM _roomserver_previous_events WHERE previous_event_id = $1`, dupeID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to query duplicate event ids")
|
||||
}
|
||||
defer internal.CloseAndLogIfError(ctx, dupeNIDsRows, "failed to close rows")
|
||||
var dupeNIDs []int64
|
||||
for dupeNIDsRows.Next() {
|
||||
var nids pq.Int64Array
|
||||
if err = dupeNIDsRows.Scan(&nids); err != nil {
|
||||
return err
|
||||
}
|
||||
dupeNIDs = append(dupeNIDs, nids...)
|
||||
}
|
||||
|
||||
if dupeNIDsRows.Err() != nil {
|
||||
return dupeNIDsRows.Err()
|
||||
}
|
||||
// dedupe NIDs
|
||||
dupeNIDs = dupeNIDs[:util.SortAndUnique(nids(dupeNIDs))]
|
||||
// now that we have all NIDs, check which room they belong to
|
||||
var roomCount int
|
||||
err = tx.QueryRowContext(ctx, `SELECT count(distinct room_nid) FROM roomserver_events WHERE event_nid IN ($1)`, pq.Array(dupeNIDs)).Scan(&roomCount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// if the events are from different rooms, that's bad and we can't continue
|
||||
if roomCount > 1 {
|
||||
return fmt.Errorf("detected events (%v) referenced for different rooms (%v)", dupeNIDs, roomCount)
|
||||
}
|
||||
// otherwise delete the dupes
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM _roomserver_previous_events WHERE previous_event_id = $1", dupeID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to delete duplicates: %w", err)
|
||||
}
|
||||
|
||||
// insert combined values
|
||||
_, err = tx.ExecContext(ctx, "INSERT INTO _roomserver_previous_events (previous_event_id, event_nids) VALUES ($1, $2)", dupeID, pq.Array(dupeNIDs))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to insert new event NIDs: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// move data
|
||||
if _, err := tx.ExecContext(ctx, `
|
||||
if _, err = tx.ExecContext(ctx, `
|
||||
INSERT
|
||||
INTO roomserver_previous_events (
|
||||
previous_event_id, event_nids
|
||||
|
|
@ -64,9 +132,15 @@ INSERT
|
|||
return fmt.Errorf("tx.ExecContext: %w", err)
|
||||
}
|
||||
// drop old table
|
||||
_, err := tx.ExecContext(ctx, `DROP TABLE _roomserver_previous_events;`)
|
||||
_, err = tx.ExecContext(ctx, `DROP TABLE _roomserver_previous_events;`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute upgrade: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type nids []int64
|
||||
|
||||
func (s nids) Len() int { return len(s) }
|
||||
func (s nids) Less(i, j int) bool { return s[i] < s[j] }
|
||||
func (s nids) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
package deltas
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/dendrite/test"
|
||||
"github.com/matrix-org/dendrite/test/testrig"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUpDropEventReferenceSHAPrevEvents(t *testing.T) {
|
||||
|
||||
cfg, ctx, close := testrig.CreateConfig(t, test.DBTypeSQLite)
|
||||
defer close()
|
||||
|
||||
db, err := sqlutil.Open(&cfg.RoomServer.Database, sqlutil.NewExclusiveWriter())
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, db)
|
||||
defer db.Close()
|
||||
|
||||
// create the table in the old layout
|
||||
_, err = db.ExecContext(ctx.Context(), `
|
||||
CREATE TABLE IF NOT EXISTS roomserver_previous_events (
|
||||
previous_event_id TEXT NOT NULL,
|
||||
previous_reference_sha256 BLOB,
|
||||
event_nids TEXT NOT NULL,
|
||||
UNIQUE (previous_event_id, previous_reference_sha256)
|
||||
);`)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// create the events table as well, slimmed down with one eventNID
|
||||
_, err = db.ExecContext(ctx.Context(), `
|
||||
CREATE TABLE IF NOT EXISTS roomserver_events (
|
||||
event_nid INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
room_nid INTEGER NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO roomserver_events (event_nid, room_nid) VALUES (1, 1)
|
||||
`)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// insert duplicate prev events with different event_nids
|
||||
stmt, err := db.PrepareContext(ctx.Context(), `INSERT INTO roomserver_previous_events (previous_event_id, event_nids, previous_reference_sha256) VALUES ($1, $2, $3)`)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, stmt)
|
||||
_, err = stmt.ExecContext(ctx.Context(), "1", "{1,2}", "a")
|
||||
assert.Nil(t, err)
|
||||
_, err = stmt.ExecContext(ctx.Context(), "1", "{1,2,3}", "b")
|
||||
assert.Nil(t, err)
|
||||
|
||||
// execute the migration
|
||||
txn, err := db.Begin()
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, txn)
|
||||
err = UpDropEventReferenceSHAPrevEvents(ctx.Context(), txn)
|
||||
defer txn.Rollback()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
@ -314,11 +314,12 @@ func (r *messagesReq) retrieveEvents() (
|
|||
clientEvents []synctypes.ClientEvent, start,
|
||||
end types.TopologyToken, err error,
|
||||
) {
|
||||
emptyToken := types.TopologyToken{}
|
||||
// Retrieve the events from the local database.
|
||||
streamEvents, err := r.snapshot.GetEventsInTopologicalRange(r.ctx, r.from, r.to, r.roomID, r.filter, r.backwardOrdering)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("GetEventsInRange: %w", err)
|
||||
return
|
||||
return []synctypes.ClientEvent{}, emptyToken, emptyToken, err
|
||||
}
|
||||
|
||||
var events []*rstypes.HeaderedEvent
|
||||
|
|
@ -333,11 +334,11 @@ func (r *messagesReq) retrieveEvents() (
|
|||
// on the ordering), or we've reached a backward extremity.
|
||||
if len(streamEvents) == 0 {
|
||||
if events, err = r.handleEmptyEventsSlice(); err != nil {
|
||||
return
|
||||
return []synctypes.ClientEvent{}, emptyToken, emptyToken, err
|
||||
}
|
||||
} else {
|
||||
if events, err = r.handleNonEmptyEventsSlice(streamEvents); err != nil {
|
||||
return
|
||||
return []synctypes.ClientEvent{}, emptyToken, emptyToken, err
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -489,7 +489,7 @@ func (p *PDUStreamProvider) getJoinResponseForCompleteSync(
|
|||
|
||||
stateEvents, err := snapshot.CurrentState(ctx, roomID, stateFilter, excludingEventIDs)
|
||||
if err != nil {
|
||||
return
|
||||
return jr, err
|
||||
}
|
||||
|
||||
jr.Summary, err = snapshot.GetRoomSummary(ctx, roomID, device.UserID)
|
||||
|
|
@ -542,7 +542,7 @@ func (p *PDUStreamProvider) getJoinResponseForCompleteSync(
|
|||
}
|
||||
backwardTopologyPos, backwardStreamPos, err = snapshot.PositionInTopology(ctx, event.EventID())
|
||||
if err != nil {
|
||||
return
|
||||
return jr, err
|
||||
}
|
||||
prevBatch = &types.TopologyToken{
|
||||
Depth: backwardTopologyPos,
|
||||
|
|
|
|||
|
|
@ -644,7 +644,7 @@ func (d *Database) CreateDevice(
|
|||
for i := 1; i <= 5; i++ {
|
||||
newDeviceID, returnErr = generateDeviceID()
|
||||
if returnErr != nil {
|
||||
return
|
||||
return nil, returnErr
|
||||
}
|
||||
|
||||
returnErr = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||||
|
|
@ -653,7 +653,7 @@ func (d *Database) CreateDevice(
|
|||
return err
|
||||
})
|
||||
if returnErr == nil {
|
||||
return
|
||||
return dev, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue