Handle createRoom & sendPDUs

This commit is contained in:
Devon Hudson 2023-10-04 11:21:23 -06:00
parent 3d02c81031
commit a5ba533cfb
No known key found for this signature in database
GPG key ID: CD06B18E77F6A628
8 changed files with 806 additions and 2 deletions

View file

@ -48,6 +48,7 @@ type createRoomRequest struct {
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
PowerLevelContentOverride json.RawMessage `json:"power_level_content_override"`
IsDirect bool `json:"is_direct"`
SenderID string `json:"sender_id"`
}
func (r createRoomRequest) Validate() *util.JSONResponse {
@ -107,6 +108,208 @@ type createRoomResponse struct {
RoomAlias string `json:"room_alias,omitempty"` // in synapse not spec
}
// CreateRoomCryptoIDs implements /createRoom
func CreateRoomCryptoIDs(
req *http.Request, device *api.Device,
cfg *config.ClientAPI,
profileAPI api.ClientUserAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
asAPI appserviceAPI.AppServiceInternalAPI,
) util.JSONResponse {
var createRequest createRoomRequest
resErr := httputil.UnmarshalJSONRequest(req, &createRequest)
if resErr != nil {
return *resErr
}
if resErr = createRequest.Validate(); resErr != nil {
return *resErr
}
evTime, err := httputil.ParseTSParam(req)
if err != nil {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.InvalidParam(err.Error()),
}
}
return makeCreateRoomEvents(req.Context(), createRequest, device, cfg, profileAPI, rsAPI, asAPI, evTime)
}
func makeCreateRoomEvents(
ctx context.Context,
createRequest createRoomRequest, device *api.Device,
cfg *config.ClientAPI,
profileAPI api.ClientUserAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
asAPI appserviceAPI.AppServiceInternalAPI,
evTime time.Time,
) util.JSONResponse {
userID, err := spec.NewUserID(device.UserID, true)
if err != nil {
util.GetLogger(ctx).WithError(err).Error("invalid userID")
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{},
}
}
if !cfg.Matrix.IsLocalServerName(userID.Domain()) {
return util.JSONResponse{
Code: http.StatusForbidden,
JSON: spec.Forbidden(fmt.Sprintf("User domain %q not configured locally", userID.Domain())),
}
}
logger := util.GetLogger(ctx)
// 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 := rsAPI.DefaultRoomVersion()
if createRequest.RoomVersion != "" {
candidateVersion := gomatrixserverlib.RoomVersion(createRequest.RoomVersion)
_, roomVersionError := roomserverVersion.SupportedRoomVersion(candidateVersion)
if roomVersionError != nil {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.UnsupportedRoomVersion(roomVersionError.Error()),
}
}
roomVersion = candidateVersion
}
logger.WithFields(log.Fields{
"userID": userID.String(),
"roomID": roomID.String(),
"roomVersion": roomVersion,
}).Info("Creating new room")
profile, err := appserviceAPI.RetrieveUserProfile(ctx, userID.String(), asAPI, profileAPI)
if err != nil {
util.GetLogger(ctx).WithError(err).Error("appserviceAPI.RetrieveUserProfile failed")
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{},
}
}
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,
SenderID: createRequest.SenderID,
}
createEvents, err := rsAPI.PerformCreateRoomCryptoIDs(ctx, *userID, *roomID, &req)
if err != nil {
util.GetLogger(ctx).WithError(err).Error("MakeCreateRoomEvents failed")
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{Err: err.Error()},
}
}
response := createRoomCryptoIDsResponse{
RoomID: roomID.String(),
Version: string(roomVersion),
PDUs: ToProtoEvents(ctx, createEvents, rsAPI),
}
return util.JSONResponse{
Code: 200,
JSON: response,
}
}
type createRoomCryptoIDsResponse struct {
RoomID string `json:"room_id"`
Version string `json:"room_version"`
PDUs []json.RawMessage `json:"pdus"`
}
func ToProtoEvents(ctx context.Context, events []gomatrixserverlib.PDU, rsAPI roomserverAPI.ClientRoomserverAPI) []json.RawMessage {
result := make([]json.RawMessage, len(events))
for i, event := range events {
result[i] = json.RawMessage(event.JSON())
//fmt.Printf("\nProcessing %s event (%s)\n", events[i].Type(), events[i].EventID())
//var rawJson interface{}
//json.Unmarshal(events[i].JSON(), &rawJson)
//fmt.Printf("JSON: %+v\n", rawJson)
//result[i] = gomatrixserverlib.ProtoEvent{
// SenderID: string(events[i].SenderID()),
// RoomID: events[i].RoomID().String(),
// Type: events[i].Type(),
// StateKey: events[i].StateKey(),
// PrevEvents: events[i].PrevEventIDs(),
// AuthEvents: events[i].AuthEventIDs(),
// Redacts: events[i].Redacts(),
// Depth: events[i].Depth(),
// Content: events[i].Content(),
// Unsigned: events[i].Unsigned(),
// Hashes: events[i].Hashes(),
// OriginServerTimestamp: events[i].OriginServerTS(),
//}
//roomVersion, _ := rsAPI.QueryRoomVersionForRoom(ctx, events[i].RoomID().String())
//verImpl, _ := gomatrixserverlib.GetRoomVersion(roomVersion)
//eventJSON, err := json.Marshal(result[i])
//if err != nil {
// util.GetLogger(ctx).WithError(err).Error("failed marshalling event")
// continue
//}
//pdu, err := verImpl.NewEventFromUntrustedJSON(eventJSON)
//if err != nil {
// util.GetLogger(ctx).WithError(err).Error("failed making event from json")
// continue
//}
//fmt.Printf("\nProcessing %s event (%s) - PDU\n", result[i].Type, pdu.EventID())
//fmt.Printf(" EventID: %v - %v\n", events[i].EventID(), pdu.EventID())
//fmt.Printf(" SenderID: %s - %s\n", events[i].SenderID(), pdu.SenderID())
//fmt.Printf(" RoomID: %s - %s\n", events[i].RoomID().String(), pdu.RoomID().String())
//fmt.Printf(" Type: %s - %s\n", events[i].Type(), pdu.Type())
//fmt.Printf(" StateKey: %s - %s\n", *events[i].StateKey(), *pdu.StateKey())
//fmt.Printf(" PrevEvents: %v - %v\n", events[i].PrevEventIDs(), pdu.PrevEventIDs())
//fmt.Printf(" AuthEvents: %v - %v\n", events[i].AuthEventIDs(), pdu.AuthEventIDs())
//fmt.Printf(" Redacts: %s - %s\n", events[i].Redacts(), pdu.Redacts())
//fmt.Printf(" Depth: %d - %d\n", events[i].Depth(), pdu.Depth())
//fmt.Printf(" Content: %v - %v\n", events[i].Content(), pdu.Content())
//fmt.Printf(" Unsigned: %v - %v\n", events[i].Unsigned(), pdu.Unsigned())
//fmt.Printf(" Hashes: %v - %v\n", events[i].Hashes(), pdu.Hashes())
//fmt.Printf(" OriginServerTS: %d - %d\n", events[i].OriginServerTS(), pdu.OriginServerTS())
//json.Unmarshal(eventJSON, &rawJson)
//fmt.Printf("JSON: %+v\n", rawJson)
}
return result
}
// CreateRoom implements /createRoom
func CreateRoom(
req *http.Request, device *api.Device,
@ -132,7 +335,6 @@ func CreateRoom(
return createRoom(req.Context(), createRequest, device, cfg, profileAPI, rsAPI, asAPI, evTime)
}
// createRoom implements /createRoom
func createRoom(
ctx context.Context,
createRequest createRoomRequest, device *api.Device,

View file

@ -312,6 +312,24 @@ func Setup(
return CreateRoom(req, device, cfg, userAPI, rsAPI, asAPI)
}),
).Methods(http.MethodPost, http.MethodOptions)
unstableMux.Handle("/org.matrix.msc_cryptoids/createRoom",
httputil.MakeAuthAPI("createRoom", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
logrus.Info("Processing request to /org.matrix.msc_cryptoids/createRoom")
return CreateRoomCryptoIDs(req, device, cfg, userAPI, rsAPI, asAPI)
}),
).Methods(http.MethodPost, http.MethodOptions)
unstableMux.Handle("/org.matrix.msc_cryptoids/sendPDUs",
httputil.MakeAuthAPI("send_pdus", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
logrus.Info("Processing request to /org.matrix.msc_cryptoids/sendPDUs")
if r := rateLimits.Limit(req, device); r != nil {
return *r
}
// NOTE: when making events such as for create_room, multiple PDUs will need to be passed between the client & server.
return SendPDUs(req, device, cfg, userAPI, rsAPI, asAPI)
}),
).Methods(http.MethodPost, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/join/{roomIDOrAlias}",
httputil.MakeAuthAPI(spec.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
@ -336,6 +354,7 @@ func Setup(
).Methods(http.MethodPost, http.MethodOptions)
if mscCfg.Enabled("msc2753") {
// TODO: update for cryptoIDs
v3mux.Handle("/peek/{roomIDOrAlias}",
httputil.MakeAuthAPI(spec.Peek, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
@ -356,6 +375,7 @@ func Setup(
return GetJoinedRooms(req, device, rsAPI)
}, httputil.WithAllowGuests()),
).Methods(http.MethodGet, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/rooms/{roomID}/join",
httputil.MakeAuthAPI(spec.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
@ -378,6 +398,7 @@ func Setup(
return resp.(util.JSONResponse)
}, httputil.WithAllowGuests()),
).Methods(http.MethodPost, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/rooms/{roomID}/leave",
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
@ -392,6 +413,7 @@ func Setup(
)
}, httputil.WithAllowGuests()),
).Methods(http.MethodPost, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/rooms/{roomID}/unpeek",
httputil.MakeAuthAPI("unpeek", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@ -403,6 +425,7 @@ func Setup(
)
}),
).Methods(http.MethodPost, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/rooms/{roomID}/ban",
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@ -412,6 +435,7 @@ func Setup(
return SendBan(req, userAPI, device, vars["roomID"], cfg, rsAPI, asAPI)
}),
).Methods(http.MethodPost, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/rooms/{roomID}/invite",
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil {
@ -424,6 +448,7 @@ func Setup(
return SendInvite(req, userAPI, device, vars["roomID"], cfg, rsAPI, asAPI)
}),
).Methods(http.MethodPost, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/rooms/{roomID}/kick",
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@ -433,6 +458,7 @@ func Setup(
return SendKick(req, userAPI, device, vars["roomID"], cfg, rsAPI, asAPI)
}),
).Methods(http.MethodPost, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/rooms/{roomID}/unban",
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@ -442,6 +468,7 @@ func Setup(
return SendUnban(req, userAPI, device, vars["roomID"], cfg, rsAPI, asAPI)
}),
).Methods(http.MethodPost, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/rooms/{roomID}/send/{eventType}",
httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@ -451,6 +478,7 @@ func Setup(
return SendEvent(req, device, vars["roomID"], vars["eventType"], nil, nil, cfg, rsAPI, nil)
}, httputil.WithAllowGuests()),
).Methods(http.MethodPost, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/rooms/{roomID}/send/{eventType}/{txnID}",
httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@ -499,6 +527,7 @@ func Setup(
return OnIncomingStateTypeRequest(req.Context(), device, rsAPI, vars["roomID"], vars["type"], vars["stateKey"], eventFormat)
}, httputil.WithAllowGuests())).Methods(http.MethodGet, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/rooms/{roomID}/state/{eventType:[^/]+/?}",
httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@ -511,6 +540,7 @@ func Setup(
}, httputil.WithAllowGuests()),
).Methods(http.MethodPut, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/rooms/{roomID}/state/{eventType}/{stateKey}",
httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@ -559,6 +589,7 @@ func Setup(
}),
).Methods(http.MethodGet, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/directory/room/{roomAlias}",
httputil.MakeAuthAPI("directory_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@ -569,6 +600,7 @@ func Setup(
}),
).Methods(http.MethodPut, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/directory/room/{roomAlias}",
httputil.MakeAuthAPI("directory_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@ -588,6 +620,7 @@ func Setup(
}),
).Methods(http.MethodGet, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/directory/list/room/{roomID}",
httputil.MakeAuthAPI("directory_list", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@ -597,6 +630,7 @@ func Setup(
return SetVisibility(req, rsAPI, device, vars["roomID"])
}),
).Methods(http.MethodPut, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/directory/list/appservice/{networkID}/{roomID}",
httputil.MakeAuthAPI("directory_list", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@ -608,6 +642,7 @@ func Setup(
).Methods(http.MethodPut, http.MethodOptions)
// Undocumented endpoint
// TODO: update for cryptoIDs
v3mux.Handle("/directory/list/appservice/{networkID}/{roomID}",
httputil.MakeAuthAPI("directory_list", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@ -648,6 +683,7 @@ func Setup(
return SendTyping(req, device, vars["roomID"], vars["userID"], rsAPI, syncProducer)
}),
).Methods(http.MethodPut, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/rooms/{roomID}/redact/{eventID}",
httputil.MakeAuthAPI("rooms_redact", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@ -657,6 +693,7 @@ func Setup(
return SendRedaction(req, device, vars["roomID"], vars["eventID"], cfg, rsAPI, nil, nil)
}),
).Methods(http.MethodPost, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/rooms/{roomID}/redact/{eventID}/{txnId}",
httputil.MakeAuthAPI("rooms_redact", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@ -668,6 +705,7 @@ func Setup(
}),
).Methods(http.MethodPut, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/sendToDevice/{eventType}/{txnID}",
httputil.MakeAuthAPI("send_to_device", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@ -1144,6 +1182,7 @@ func Setup(
}),
).Methods(http.MethodPost, http.MethodOptions)
// TODO: update for cryptoIDs
v3mux.Handle("/rooms/{roomID}/upgrade",
httputil.MakeAuthAPI("rooms_upgrade", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))

View file

@ -0,0 +1,140 @@
// 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 routing
import (
"encoding/json"
"net/http"
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
"github.com/matrix-org/dendrite/clientapi/httputil"
roomserverAPI "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/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util"
)
type sendPDUsRequest struct {
Version string `json:"room_version"`
PDUs []json.RawMessage `json:"pdus"`
}
// SendPDUs implements /sendPDUs
func SendPDUs(
req *http.Request, device *api.Device,
cfg *config.ClientAPI,
profileAPI api.ClientUserAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
asAPI appserviceAPI.AppServiceInternalAPI,
) util.JSONResponse {
// TODO: cryptoIDs - should this include an "eventType"?
// if it's a bulk send endpoint, I don't think that makes any sense since there are multiple event types
// In that case, how do I know how to treat the events?
// I could sort them all by roomID?
// Then filter them down based on event type? (how do I collect groups of events such as for room creation?)
// Possibly based on event hash tracking that I know were sent to the client?
// For createRoom, I know what the possible list of events are, so try to find those and collect them to send to room creation.
// Could also sort by depth... but that seems dangerous and depth may not be a field forever
// Does it matter at all?
// Can't I just forward all the events to the roomserver?
// Do I need to do any specific processing on them?
var pdus sendPDUsRequest
resErr := httputil.UnmarshalJSONRequest(req, &pdus)
if resErr != nil {
return *resErr
}
userID, err := spec.NewUserID(device.UserID, true)
if err != nil {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.InvalidParam(err.Error()),
}
}
inputs := make([]roomserverAPI.InputRoomEvent, 0, len(pdus.PDUs))
for _, event := range pdus.PDUs {
// TODO: cryptoIDs - event hash check?
verImpl, err := gomatrixserverlib.GetRoomVersion(gomatrixserverlib.RoomVersion(pdus.Version))
if err != nil {
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{Err: err.Error()},
}
}
//eventJSON, err := json.Marshal(event)
//if err != nil {
// return util.JSONResponse{
// Code: http.StatusInternalServerError,
// JSON: spec.InternalServerError{Err: err.Error()},
// }
//}
// TODO: cryptoIDs - how should we be converting to a PDU here?
// if the hash matches an event we sent to the client, then the JSON should be good.
// But how do we know how to fill out if the event is redacted if we use the trustedJSON function?
// Also - untrusted JSON seems better - except it strips off the unsigned field?
// Also - gmsl events don't store the `hashes` field... problem?
pdu, err := verImpl.NewEventFromUntrustedJSON(event)
if err != nil {
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{Err: err.Error()},
}
}
key, err := rsAPI.GetOrCreateUserRoomPrivateKey(req.Context(), *userID, pdu.RoomID())
if err != nil {
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{Err: err.Error()},
}
}
pdu = pdu.Sign(string(pdu.SenderID()), "ed25519:1", key)
util.GetLogger(req.Context()).Infof("Processing %s event (%s)", pdu.Type(), pdu.EventID())
switch pdu.Type() {
case spec.MRoomCreate:
}
// TODO: cryptoIDs - does it matter which order these are added?
// yes - if the events are for room creation.
// Could make this a client requirement? ie. events are processed based on the order they appear
// We need to check event validity after processing each event.
// ie. what if the client changes power levels that disallow further events they sent?
// We should be doing this already as part of `SendInputRoomEvents`, but how should we pass this
// failure back to the client?
inputs = append(inputs, roomserverAPI.InputRoomEvent{
Kind: roomserverAPI.KindNew,
Event: &types.HeaderedEvent{PDU: pdu},
Origin: userID.Domain(),
SendAsServer: roomserverAPI.DoNotSendToOtherServers,
})
}
// send the events to the roomserver
if err := roomserverAPI.SendInputRoomEvents(req.Context(), rsAPI, userID.Domain(), inputs, false); err != nil {
util.GetLogger(req.Context()).WithError(err).Error("roomserverAPI.SendInputRoomEvents failed")
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{Err: err.Error()},
}
}
return util.JSONResponse{
Code: http.StatusOK,
}
}

2
go.mod
View file

@ -1,5 +1,7 @@
module github.com/matrix-org/dendrite
replace github.com/matrix-org/gomatrixserverlib => ../../gomatrixserverlib/crypto-ids/
require (
github.com/Arceliar/ironwood v0.0.0-20221025225125-45b4281814c2
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979

View file

@ -232,6 +232,7 @@ type ClientRoomserverAPI interface {
GetRoomIDForAlias(ctx context.Context, req *GetRoomIDForAliasRequest, res *GetRoomIDForAliasResponse) error
GetAliasesForRoomID(ctx context.Context, req *GetAliasesForRoomIDRequest, res *GetAliasesForRoomIDResponse) error
PerformCreateRoomCryptoIDs(ctx context.Context, userID spec.UserID, roomID spec.RoomID, createRequest *PerformCreateRoomRequest) ([]gomatrixserverlib.PDU, 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 string, userID spec.UserID, roomVersion gomatrixserverlib.RoomVersion) (newRoomID string, err error)

View file

@ -29,6 +29,8 @@ type PerformCreateRoomRequest struct {
KeyID gomatrixserverlib.KeyID
PrivateKey ed25519.PrivateKey
EventTime time.Time
SenderID string
}
type PerformJoinRequest struct {

View file

@ -44,6 +44,423 @@ type Creator struct {
RSAPI api.RoomserverInternalAPI
}
// PerformCreateRoomCryptoIDs handles all the steps necessary to create a new room.
// nolint: gocyclo
func (c *Creator) PerformCreateRoomCryptoIDs(ctx context.Context, userID spec.UserID, roomID spec.RoomID, createRequest *api.PerformCreateRoomRequest) ([]gomatrixserverlib.PDU, error) {
verImpl, err := gomatrixserverlib.GetRoomVersion(createRequest.RoomVersion)
if err != nil {
return nil, 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 nil, spec.BadJSON("invalid create content")
}
}
senderID := spec.SenderID(createRequest.SenderID)
// TODO: cryptoIDs - should we be assigning a room NID yet?
_, err = c.DB.AssignRoomNID(ctx, roomID, createRequest.RoomVersion)
if err != nil {
util.GetLogger(ctx).WithError(err).Error("failed to assign roomNID")
return nil, spec.InternalServerError{Err: err.Error()}
}
if createRequest.RoomVersion == gomatrixserverlib.RoomVersionPseudoIDs {
util.GetLogger(ctx).Infof("StoreUserRoomPublicKey - SenderID: %s UserID: %s RoomID: %s", senderID, userID.String(), roomID.String())
bytes := spec.Base64Bytes{}
err = bytes.Decode(string(senderID))
if err != nil {
return nil, err
}
if len(bytes) != ed25519.PublicKeySize {
return nil, spec.BadJSON("SenderID is not a valid ed25519 public key")
}
// TODO: cryptoIDs - Swap this out for only storing the public key
key, keyErr := c.RSAPI.GetOrCreateUserRoomPrivateKey(ctx, userID, roomID)
if keyErr != nil {
util.GetLogger(ctx).WithError(keyErr).Error("GetOrCreateUserRoomPrivateKey failed")
return nil, spec.InternalServerError{Err: keyErr.Error()}
}
senderID = spec.SenderIDFromPseudoIDKey(key)
//err := c.RSAPI.StoreUserRoomPublicKey(ctx, senderID, userID, roomID)
//if err != nil {
// return nil, spec.InternalServerError{Err: err.Error()}
//}
}
createContent["creator"] = senderID
createContent["room_version"] = createRequest.RoomVersion
powerLevelContent := eventutil.InitialPowerLevelsContent(string(senderID))
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 nil, 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: string(senderID),
}
memberContent := gomatrixserverlib.MemberContent{
Membership: spec.Join,
DisplayName: createRequest.UserDisplayName,
AvatarURL: createRequest.UserAvatarURL,
}
// If we are creating a room with pseudo IDs, create and sign the MXIDMapping
if createRequest.RoomVersion == gomatrixserverlib.RoomVersionPseudoIDs {
mapping := &gomatrixserverlib.MXIDMapping{
UserRoomKey: senderID,
UserID: userID.String(),
}
identity, idErr := c.Cfg.Matrix.SigningIdentityFor(userID.Domain()) // we MUST use the server signing mxid_mapping
if idErr != nil {
logrus.WithError(idErr).WithField("domain", userID.Domain()).Error("unable to find signing identity for domain")
return nil, spec.InternalServerError{Err: idErr.Error()}
}
// Sign the mapping with the server identity
if err = mapping.Sign(identity.ServerName, identity.KeyID, identity.PrivateKey); err != nil {
return nil, spec.InternalServerError{Err: err.Error()}
}
memberContent.MXIDMapping = mapping
}
membershipEvent.Content = memberContent
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 nil, spec.InternalServerError{Err: err.Error()}
}
if aliasResp.RoomID != "" {
return nil, 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.
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)
}
var builtEvents []gomatrixserverlib.PDU
authEvents := gomatrixserverlib.NewAuthEvents(nil)
if err != nil {
util.GetLogger(ctx).WithError(err).Error("rsapi.QuerySenderIDForUser failed")
return nil, spec.InternalServerError{Err: err.Error()}
}
for i, e := range eventsToMake {
depth := i + 1 // depth starts at 1
builder := verImpl.NewEventBuilderFromProtoEvent(&gomatrixserverlib.ProtoEvent{
SenderID: string(senderID),
RoomID: roomID.String(),
Type: e.Type,
StateKey: &e.StateKey,
Depth: int64(depth),
})
err = builder.SetContent(e.Content)
if err != nil {
util.GetLogger(ctx).WithError(err).Error("builder.SetContent failed")
return nil, spec.InternalServerError{Err: err.Error()}
}
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 nil, spec.InternalServerError{Err: err.Error()}
}
ev, err = builder.BuildWithoutSigning(createRequest.EventTime, userID.Domain())
if err != nil {
util.GetLogger(ctx).WithError(err).Error("buildEvent failed")
return nil, spec.InternalServerError{Err: err.Error()}
}
if err = gomatrixserverlib.Allowed(ev, &authEvents, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
return c.RSAPI.QueryUserIDForSender(ctx, roomID, senderID)
}); err != nil {
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.Allowed failed")
return nil, spec.InternalServerError{Err: err.Error()}
}
// Add the event to the list of auth events
builtEvents = append(builtEvents, ev)
err = authEvents.AddEvent(ev)
if err != nil {
util.GetLogger(ctx).WithError(err).Error("authEvents.AddEvent failed")
return nil, spec.InternalServerError{Err: err.Error()}
}
}
// 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 != "" {
aliasAlreadyExists, aliasErr := c.RSAPI.SetRoomAlias(ctx, senderID, roomID, roomAlias)
if aliasErr != nil {
util.GetLogger(ctx).WithError(aliasErr).Error("aliasAPI.SetRoomAlias failed")
return nil, spec.InternalServerError{Err: aliasErr.Error()}
}
if aliasAlreadyExists {
return nil, spec.RoomInUse("Room alias already exists.")
}
}
// TODO: cryptoIDs - this shouldn't really be done until the client calls /sendPDUs with these events
// But that would require the visibility setting also being passed along
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 nil, spec.InternalServerError{Err: err.Error()}
}
}
// 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
// globalStrippedState = append(
// globalStrippedState,
// gomatrixserverlib.NewInviteStrippedState(ev),
// )
// }
//}
// Process the invites.
//for _, invitee := range createRequest.InvitedUsers {
//inviteeUserID, userIDErr := spec.NewUserID(invitee, true)
//if userIDErr != nil {
// util.GetLogger(ctx).WithError(userIDErr).Error("invalid UserID")
// return nil, spec.InternalServerError{}
//}
// TODO: cryptoIDs - these shouldn't be here
// instead we should return proto invite events?
//err = c.RSAPI.PerformInvite(ctx, &api.PerformInviteRequest{
// InviteInput: api.InviteInput{
// RoomID: roomID,
// Inviter: userID,
// Invitee: *inviteeUserID,
// DisplayName: createRequest.UserDisplayName,
// AvatarURL: createRequest.UserAvatarURL,
// Reason: "",
// IsDirect: createRequest.IsDirect,
// KeyID: createRequest.KeyID,
// PrivateKey: createRequest.PrivateKey,
// EventTime: createRequest.EventTime,
// },
// InviteRoomState: globalStrippedState,
// SendAsServer: string(userID.Domain()),
//})
//switch e := err.(type) {
//case api.ErrInvalidID:
// return nil, spec.Unknown(e.Error())
//case api.ErrNotAllowed:
// return nil, spec.Forbidden(e.Error())
//case nil:
//default:
// util.GetLogger(ctx).WithError(err).Error("PerformInvite failed")
// sentry.CaptureException(err)
// return nil, spec.InternalServerError{}
//}
//}
//}
return builtEvents, nil
}
// 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) {

View file

@ -16,7 +16,8 @@ type RoomServer struct {
}
func (c *RoomServer) Defaults(opts DefaultOpts) {
c.DefaultRoomVersion = gomatrixserverlib.RoomVersionV10
//c.DefaultRoomVersion = gomatrixserverlib.RoomVersionV10
c.DefaultRoomVersion = gomatrixserverlib.RoomVersionPseudoIDs
if opts.Generate {
if !opts.SingleDatabase {
c.Database.ConnectionString = "file:roomserver.db"