diff --git a/clientapi/routing/createroom.go b/clientapi/routing/createroom.go index dfc15173..a384e075 100644 --- a/clientapi/routing/createroom.go +++ b/clientapi/routing/createroom.go @@ -176,12 +176,6 @@ func createRoom( 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") @@ -197,6 +191,49 @@ func createRoom( keyID := cfg.Matrix.KeyID privateKey := cfg.Matrix.PrivateKey + verImpl := gomatrixserverlib.MustGetRoomVersion(roomVersion) + + var createEventJSON json.RawMessage + if verImpl.DomainlessRoomIDs() { + // make the create event up-front so the roomserver can calculate the room NID to store. + var additionalCreators []string + if createRequest.Preset == spec.PresetTrustedPrivateChat { + additionalCreators = createRequest.Invite + } + createContent, err := roomserverAPI.GenerateCreateContent(ctx, createRequest.RoomVersion, userID.String(), createRequest.CreationContent, additionalCreators) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("GenerateCreateContent failed") + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: spec.BadJSON("invalid create content"), + } + } + authEvents, _ := gomatrixserverlib.NewAuthEvents(nil) + identity, err := cfg.Matrix.SigningIdentityFor(userID.Domain()) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("Failed to get signing identity") + return util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: spec.InternalServerError{}, + } + } + createEvent, jsonErr := roomserverAPI.GeneratePDU( + ctx, gomatrixserverlib.MustGetRoomVersion(roomVersion), + gomatrixserverlib.FledglingEvent{ + Type: spec.MRoomCreate, + Content: createContent, + }, + authEvents, 1, "", identity, evTime, userID.String(), "", rsAPI, + ) + if jsonErr != nil { + util.GetLogger(ctx).WithError(err).Error("Failed to make the create event") + return *jsonErr + } + createEventJSON = createEvent.JSON() + r := createEvent.RoomID() + roomID = &r + } + req := roomserverAPI.PerformCreateRoomRequest{ InvitedUsers: createRequest.Invite, RoomName: createRequest.Name, @@ -204,6 +241,7 @@ func createRoom( Topic: createRequest.Topic, StatePreset: createRequest.Preset, CreationContent: createRequest.CreationContent, + CreateEvent: createEventJSON, InitialState: createRequest.InitialState, RoomAliasName: createRequest.RoomAliasName, RoomVersion: roomVersion, @@ -217,6 +255,12 @@ func createRoom( EventTime: evTime, } + logger.WithFields(log.Fields{ + "userID": userID.String(), + "roomID": roomID.String(), + "roomVersion": roomVersion, + }).Info("Creating new room") + roomAlias, createRes := rsAPI.PerformCreateRoom(ctx, *userID, *roomID, &req) if createRes != nil { return *createRes diff --git a/clientapi/routing/directory.go b/clientapi/routing/directory.go index ef8e3075..bd4d4580 100644 --- a/clientapi/routing/directory.go +++ b/clientapi/routing/directory.go @@ -411,10 +411,11 @@ func SetVisibility( JSON: spec.InternalServerError{}, } } + privileged := isPrivilegedCreator(req.Context(), rsAPI, roomID, *senderID) // NOTSPEC: Check if the user's power is greater than power required to change m.room.canonical_alias event power, _ := gomatrixserverlib.NewPowerLevelContentFromEvent(queryEventsRes.StateEvents[0].PDU) - if power.UserLevel(*senderID) < power.EventLevel(spec.MRoomCanonicalAlias, true) { + if !privileged && power.UserLevel(*senderID) < power.EventLevel(spec.MRoomCanonicalAlias, true) { return util.JSONResponse{ Code: http.StatusForbidden, JSON: spec.Forbidden("userID doesn't have power level to change visibility"), diff --git a/clientapi/routing/membership.go b/clientapi/routing/membership.go index 9579e648..2077bf8e 100644 --- a/clientapi/routing/membership.go +++ b/clientapi/routing/membership.go @@ -11,6 +11,7 @@ import ( "crypto/ed25519" "fmt" "net/http" + "slices" "time" appserviceAPI "github.com/element-hq/dendrite/appservice/api" @@ -79,7 +80,8 @@ func SendBan( if errRes != nil { return *errRes } - allowedToBan := pl.UserLevel(*senderID) >= pl.Ban + privileged := isPrivilegedCreator(req.Context(), rsAPI, roomID, *senderID) + allowedToBan := privileged || pl.UserLevel(*senderID) >= pl.Ban if !allowedToBan { return util.JSONResponse{ Code: http.StatusForbidden, @@ -118,6 +120,12 @@ func sendMembership(ctx context.Context, profileAPI userapi.ClientUserAPI, devic false, ); err != nil { util.GetLogger(ctx).WithError(err).Error("SendEvents failed") + if err.Error() == api.InputWasRejected { + return util.JSONResponse{ + Code: http.StatusForbidden, + JSON: spec.Forbidden("the event was rejected"), + } + } return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: spec.InternalServerError{}, @@ -185,7 +193,8 @@ func SendKick( if errRes != nil { return *errRes } - allowedToKick := pl.UserLevel(*senderID) >= pl.Kick || bodyUserID.String() == deviceUserID.String() + privileged := isPrivilegedCreator(req.Context(), rsAPI, roomID, *senderID) + allowedToKick := privileged || pl.UserLevel(*senderID) >= pl.Kick || bodyUserID.String() == deviceUserID.String() if !allowedToKick { return util.JSONResponse{ Code: http.StatusForbidden, @@ -680,3 +689,12 @@ func getPowerlevels(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI, } return pl, nil } + +// Returns true if the room is a room which supports privileged creators and the sender is a creator, else false. +func isPrivilegedCreator(ctx context.Context, rsAPI roomserverAPI.ClientRoomserverAPI, roomID string, senderID spec.SenderID) bool { + createEvent := roomserverAPI.GetStateEvent(ctx, rsAPI, roomID, gomatrixserverlib.StateKeyTuple{ + EventType: spec.MRoomCreate, + StateKey: "", + }) + return gomatrixserverlib.MustGetRoomVersion(createEvent.Version()).PrivilegedCreators() && slices.Contains(gomatrixserverlib.CreatorsFromCreateEvent(createEvent), string(senderID)) +} diff --git a/clientapi/routing/redaction.go b/clientapi/routing/redaction.go index 795b311b..32acf182 100644 --- a/clientapi/routing/redaction.go +++ b/clientapi/routing/redaction.go @@ -98,10 +98,12 @@ func SendRedaction( } } + privileged := isPrivilegedCreator(req.Context(), rsAPI, roomID, *senderID) + // "Users may redact their own events, and any user with a power level greater than or equal // to the redact power level of the room may redact events there" // https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid - allowedToRedact := ev.SenderID() == *senderID + allowedToRedact := ev.SenderID() == *senderID || privileged if !allowedToRedact { plEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{ EventType: spec.MRoomPowerLevels, diff --git a/clientapi/routing/sendevent.go b/clientapi/routing/sendevent.go index ac8b2d7e..a53cb065 100644 --- a/clientapi/routing/sendevent.go +++ b/clientapi/routing/sendevent.go @@ -77,7 +77,6 @@ func SendEvent( JSON: spec.UnsupportedRoomVersion(err.Error()), } } - if txnID != nil { // Try to fetch response from transactionsCache if res, ok := txnCache.FetchTransaction(device.AccessToken, *txnID, req.URL); ok { @@ -367,6 +366,12 @@ func generateSendEvent( JSON: spec.InternalServerError{}, } } + if proto.Type == spec.MRoomCreate && proto.StateKey != nil && *proto.StateKey == "" { + return nil, &util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: spec.InvalidParam("cannot resend m.room.create event"), + } + } identity, err := rsAPI.SigningIdentityFor(ctx, *validRoomID, *fullUserID) if err != nil { @@ -424,8 +429,13 @@ func generateSendEvent( if err = gomatrixserverlib.Allowed(e.PDU, provider, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) { return rsAPI.QueryUserIDForSender(ctx, *validRoomID, senderID) }); err != nil { + code := 403 + validationErr, ok := err.(*gomatrixserverlib.EventValidationError) + if ok { + code = validationErr.Code + } return nil, &util.JSONResponse{ - Code: http.StatusForbidden, + Code: code, JSON: spec.Forbidden(err.Error()), // TODO: Is this error string comprehensible to the client? } } diff --git a/clientapi/routing/server_notices.go b/clientapi/routing/server_notices.go index f8779182..2e38c208 100644 --- a/clientapi/routing/server_notices.go +++ b/clientapi/routing/server_notices.go @@ -14,6 +14,7 @@ import ( "time" "github.com/matrix-org/gomatrix" + "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/tokens" "github.com/matrix-org/util" "github.com/prometheus/client_golang/prometheus" @@ -139,7 +140,7 @@ func SendServerNotice( // create a new room for the user if len(commonRooms) == 0 { - powerLevelContent := eventutil.InitialPowerLevelsContent(senderUserID.String()) + powerLevelContent := eventutil.InitialPowerLevelsContent(gomatrixserverlib.MustGetRoomVersion(roomVersion), senderUserID.String()) powerLevelContent.Users[r.UserID] = -10 // taken from Synapse pl, err := json.Marshal(powerLevelContent) if err != nil { diff --git a/clientapi/routing/upgrade_room.go b/clientapi/routing/upgrade_room.go index d71cdeaf..1eb35688 100644 --- a/clientapi/routing/upgrade_room.go +++ b/clientapi/routing/upgrade_room.go @@ -23,7 +23,8 @@ import ( ) type upgradeRoomRequest struct { - NewVersion string `json:"new_version"` + NewVersion string `json:"new_version"` + AdditionalCreators []string `json:"additional_creators"` } type upgradeRoomResponse struct { @@ -43,6 +44,13 @@ func UpgradeRoom( return *rErr } + if r.NewVersion == "" { + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: spec.InvalidParam("missing version to upgrade to"), + } + } + // Validate that the room version is supported if _, err := version.SupportedRoomVersion(gomatrixserverlib.RoomVersion(r.NewVersion)); err != nil { return util.JSONResponse{ @@ -59,7 +67,10 @@ func UpgradeRoom( JSON: spec.InternalServerError{}, } } - newRoomID, err := rsAPI.PerformRoomUpgrade(req.Context(), roomID, *userID, gomatrixserverlib.RoomVersion(r.NewVersion)) + newRoomID, err := rsAPI.PerformRoomUpgrade(req.Context(), roomID, *userID, gomatrixserverlib.RoomVersion(r.NewVersion), r.AdditionalCreators) + if err != nil { + util.GetLogger(req.Context()).WithError(err).Error("PerformRoomUpgrade failed") + } switch e := err.(type) { case nil: case roomserverAPI.ErrNotAllowed: diff --git a/docs/Gemfile b/docs/Gemfile index a6aa152a..fb1f2eee 100644 --- a/docs/Gemfile +++ b/docs/Gemfile @@ -1,5 +1,2 @@ source "https://rubygems.org" -gem "github-pages", "~> 226", group: :jekyll_plugins -group :jekyll_plugins do - gem "jekyll-feed", "~> 0.15.1" -end +gem "github-pages", "~> 232", group: :jekyll_plugins diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 7c9a430b..5c91a819 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -1,66 +1,65 @@ GEM remote: https://rubygems.org/ specs: - activesupport (6.0.6.1) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + activesupport (8.0.2) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + base64 (0.2.0) + benchmark (0.4.1) + bigdecimal (3.2.2) coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.11.1) + coffee-script-source (1.12.2) colorator (1.1.0) - commonmarker (0.23.10) - concurrent-ruby (1.2.0) - dnsruby (1.61.9) - simpleidn (~> 0.1) + commonmarker (0.23.11) + concurrent-ruby (1.3.5) + connection_pool (2.5.3) + csv (3.3.5) + dnsruby (1.72.4) + base64 (~> 0.2.0) + logger (~> 1.6.5) + simpleidn (~> 0.2.1) + drb (2.2.3) em-websocket (0.5.3) eventmachine (>= 0.12.9) http_parser.rb (~> 0) - ethon (0.15.0) + ethon (0.16.0) ffi (>= 1.15.0) eventmachine (1.2.7) - execjs (2.8.1) - faraday (1.10.0) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0) - faraday-multipart (~> 1.0) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.0) - faraday-patron (~> 1.0) - faraday-rack (~> 1.0) - faraday-retry (~> 1.0) - ruby2_keywords (>= 0.0.4) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.0) - faraday-excon (1.1.0) - faraday-httpclient (1.0.1) - faraday-multipart (1.0.3) - multipart-post (>= 1.2, < 3) - faraday-net_http (1.0.1) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) - faraday-rack (1.0.0) - faraday-retry (1.0.3) - ffi (1.15.5) + execjs (2.10.0) + faraday (2.13.4) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-net_http (3.4.1) + net-http (>= 0.5.0) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-linux-gnu) forwardable-extended (2.6.0) - gemoji (3.0.1) - github-pages (226) - github-pages-health-check (= 1.17.9) - jekyll (= 3.9.2) - jekyll-avatar (= 0.7.0) - jekyll-coffeescript (= 1.1.1) - jekyll-commonmark-ghpages (= 0.2.0) - jekyll-default-layout (= 0.1.4) - jekyll-feed (= 0.15.1) + gemoji (4.1.0) + github-pages (232) + github-pages-health-check (= 1.18.2) + jekyll (= 3.10.0) + jekyll-avatar (= 0.8.0) + jekyll-coffeescript (= 1.2.2) + jekyll-commonmark-ghpages (= 0.5.1) + jekyll-default-layout (= 0.1.5) + jekyll-feed (= 0.17.0) jekyll-gist (= 1.5.0) - jekyll-github-metadata (= 2.13.0) + jekyll-github-metadata (= 2.16.1) jekyll-include-cache (= 0.2.1) jekyll-mentions (= 1.6.0) jekyll-optional-front-matter (= 0.3.2) @@ -87,32 +86,34 @@ GEM jekyll-theme-tactile (= 0.2.0) jekyll-theme-time-machine (= 0.2.0) jekyll-titles-from-headings (= 0.5.3) - jemoji (= 0.12.0) - kramdown (= 2.3.2) + jemoji (= 0.13.0) + kramdown (= 2.4.0) kramdown-parser-gfm (= 1.1.0) - liquid (= 4.0.3) + liquid (= 4.0.4) mercenary (~> 0.3) minima (= 2.5.1) - nokogiri (>= 1.13.4, < 2.0) - rouge (= 3.26.0) + nokogiri (>= 1.16.2, < 2.0) + rouge (= 3.30.0) terminal-table (~> 1.4) - github-pages-health-check (1.17.9) + webrick (~> 1.8) + github-pages-health-check (1.18.2) addressable (~> 2.3) dnsruby (~> 1.60) - octokit (~> 4.0) - public_suffix (>= 3.0, < 5.0) + octokit (>= 4, < 8) + public_suffix (>= 3.0, < 6.0) typhoeus (~> 1.3) - html-pipeline (2.14.1) + html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) http_parser.rb (0.8.0) - i18n (0.9.5) + i18n (1.14.7) concurrent-ruby (~> 1.0) - jekyll (3.9.2) + jekyll (3.10.0) addressable (~> 2.4) colorator (~> 1.0) + csv (~> 3.0) em-websocket (~> 0.5) - i18n (~> 0.7) + i18n (>= 0.7, < 2) jekyll-sass-converter (~> 1.0) jekyll-watch (~> 2.0) kramdown (>= 1.17, < 3) @@ -121,27 +122,28 @@ GEM pathutil (~> 0.9) rouge (>= 1.7, < 4) safe_yaml (~> 1.0) - jekyll-avatar (0.7.0) + webrick (>= 1.0) + jekyll-avatar (0.8.0) jekyll (>= 3.0, < 5.0) - jekyll-coffeescript (1.1.1) + jekyll-coffeescript (1.2.2) coffee-script (~> 2.2) - coffee-script-source (~> 1.11.1) + coffee-script-source (~> 1.12) jekyll-commonmark (1.4.0) commonmarker (~> 0.22) - jekyll-commonmark-ghpages (0.2.0) - commonmarker (~> 0.23.4) - jekyll (~> 3.9.0) + jekyll-commonmark-ghpages (0.5.1) + commonmarker (>= 0.23.7, < 1.1.0) + jekyll (>= 3.9, < 4.0) jekyll-commonmark (~> 1.4.0) - rouge (>= 2.0, < 4.0) - jekyll-default-layout (0.1.4) - jekyll (~> 3.0) - jekyll-feed (0.15.1) + rouge (>= 2.0, < 5.0) + jekyll-default-layout (0.1.5) + jekyll (>= 3.0, < 5.0) + jekyll-feed (0.17.0) jekyll (>= 3.7, < 5.0) jekyll-gist (1.5.0) octokit (~> 4.2) - jekyll-github-metadata (2.13.0) + jekyll-github-metadata (2.16.1) jekyll (>= 3.4, < 5.0) - octokit (~> 4.0, != 4.4.0) + octokit (>= 4, < 7, != 4.4.0) jekyll-include-cache (0.2.1) jekyll (>= 3.7, < 5.0) jekyll-mentions (1.6.0) @@ -212,76 +214,71 @@ GEM jekyll (>= 3.3, < 5.0) jekyll-watch (2.2.1) listen (~> 3.0) - jemoji (0.12.0) - gemoji (~> 3.0) + jemoji (0.13.0) + gemoji (>= 3, < 5) html-pipeline (~> 2.2) jekyll (>= 3.0, < 5.0) - kramdown (2.3.2) + json (2.13.1) + kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - liquid (4.0.3) - listen (3.7.1) - rb-fsevent (~> 0.10, >= 0.10.3) + liquid (4.0.4) + listen (3.9.0) rb-inotify (~> 0.9, >= 0.9.10) + logger (1.6.6) mercenary (0.3.6) minima (2.5.1) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) - minitest (5.17.0) - multipart-post (2.1.1) - nokogiri (1.18.8-arm64-darwin) + minitest (5.25.5) + net-http (0.6.0) + uri + nokogiri (1.18.9-arm64-darwin) racc (~> 1.4) - nokogiri (1.18.8-x86_64-linux-gnu) + nokogiri (1.18.9-x86_64-linux-gnu) racc (~> 1.4) - octokit (4.22.0) - faraday (>= 0.9) - sawyer (~> 0.8.0, >= 0.5.3) + octokit (4.25.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (4.0.7) + public_suffix (5.1.1) racc (1.8.1) - rb-fsevent (0.11.1) - rb-inotify (0.10.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) ffi (~> 1.0) - rexml (3.3.2) - strscan - rouge (3.26.0) - ruby2_keywords (0.0.5) - rubyzip (2.3.2) + rexml (3.4.1) + rouge (3.30.0) + rubyzip (2.4.1) safe_yaml (1.0.5) sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - sawyer (0.8.2) + sawyer (0.9.2) addressable (>= 2.3.5) - faraday (> 0.8, < 2.0) - simpleidn (0.2.1) - unf (~> 0.1.4) - strscan (3.1.0) + faraday (>= 0.17.3, < 3) + securerandom (0.4.1) + simpleidn (0.2.3) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) - thread_safe (0.3.6) - typhoeus (1.4.0) + typhoeus (1.4.1) ethon (>= 0.9.0) - tzinfo (1.2.11) - thread_safe (~> 0.1) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.1) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) unicode-display_width (1.8.0) - zeitwerk (2.6.6) + uri (1.0.3) + webrick (1.9.1) PLATFORMS arm64-darwin-21 x86_64-linux DEPENDENCIES - github-pages (~> 226) - jekyll-feed (~> 0.15.1) + github-pages (~> 232) BUNDLED WITH 2.3.7 diff --git a/go.mod b/go.mod index 58649ed3..eea09a6d 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,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-20250619052822-904c8f04597e + github.com/matrix-org/gomatrixserverlib v0.0.0-20250811193806-b7e0e0824751 github.com/matrix-org/pinecone v0.11.1-0.20230810010612-ea4c33717fd7 github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 github.com/mattn/go-sqlite3 v1.14.28 @@ -100,6 +100,7 @@ require ( github.com/google/go-tpm v0.9.3 // indirect github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd // indirect github.com/h2non/filetype v1.1.3 // indirect + github.com/hashicorp/go-set/v3 v3.0.0 // indirect github.com/hjson/hjson-go/v4 v4.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/juju/errors v1.0.0 // indirect @@ -118,6 +119,7 @@ require ( github.com/nats-io/nkeys v0.4.11 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/oleiade/lane/v2 v2.0.0 // indirect github.com/onsi/ginkgo/v2 v2.11.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect diff --git a/go.sum b/go.sum index 35c17476..9be17422 100644 --- a/go.sum +++ b/go.sum @@ -199,6 +199,8 @@ github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/hashicorp/go-set/v3 v3.0.0 h1:CaJBQvQCOWoftrBcDt7Nwgo0kdpmrKxar/x2o6pV9JA= +github.com/hashicorp/go-set/v3 v3.0.0/go.mod h1:IEghM2MpE5IaNvL+D7X480dfNtxjRXZ6VMpK3C8s2ok= github.com/hjson/hjson-go/v4 v4.4.0 h1:D/NPvqOCH6/eisTb5/ztuIS8GUvmpHaLOcNk1Bjr298= github.com/hjson/hjson-go/v4 v4.4.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -235,8 +237,10 @@ 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-20250619052822-904c8f04597e h1:SWediqisy1Eoumr06sjGaA6gt6gS4FtXe00VB6fSNZw= -github.com/matrix-org/gomatrixserverlib v0.0.0-20250619052822-904c8f04597e/go.mod h1:61LpEsWAroRfdVh2dnr6fQ+K3MmRgD5I35GVvF4FpXQ= +github.com/matrix-org/gomatrixserverlib v0.0.0-20250811171307-390dbaa8de98 h1:AH19nhwaPYCRddS/s7LgKS+fhntFXg2qG47uFAjwFJ4= +github.com/matrix-org/gomatrixserverlib v0.0.0-20250811171307-390dbaa8de98/go.mod h1:b6KVfDjXjA5Q7vhpOaMqIhFYvu5BuFVZixlNeTV/CLc= +github.com/matrix-org/gomatrixserverlib v0.0.0-20250811193806-b7e0e0824751 h1:x1pC7Nt1Qb24q9WtPybMHWo2uVFTzCKtlUAzarju8bk= +github.com/matrix-org/gomatrixserverlib v0.0.0-20250811193806-b7e0e0824751/go.mod h1:b6KVfDjXjA5Q7vhpOaMqIhFYvu5BuFVZixlNeTV/CLc= github.com/matrix-org/pinecone v0.11.1-0.20230810010612-ea4c33717fd7 h1:6t8kJr8i1/1I5nNttw6nn1ryQJgzVlBmSGgPiiaTdw4= github.com/matrix-org/pinecone v0.11.1-0.20230810010612-ea4c33717fd7/go.mod h1:ReWMS/LoVnOiRAdq9sNUC2NZnd1mZkMNB52QhpTRWjg= github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 h1:6z4KxomXSIGWqhHcfzExgkH3Z3UkIXry4ibJS4Aqz2Y= @@ -286,6 +290,8 @@ github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJm github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oleiade/lane/v2 v2.0.0 h1:XW/ex/Inr+bPkLd3O240xrFOhUkTd4Wy176+Gv0E3Qw= +github.com/oleiade/lane/v2 v2.0.0/go.mod h1:i5FBPFAYSWCgLh58UkUGCChjcCzef/MI7PlQm2TKCeg= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= @@ -324,6 +330,8 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= +github.com/shoenig/test v1.11.0 h1:NoPa5GIoBwuqzIviCrnUJa+t5Xb4xi5Z+zODJnIDsEQ= +github.com/shoenig/test v1.11.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= diff --git a/internal/eventutil/eventcontent.go b/internal/eventutil/eventcontent.go index 1a3222f8..2dab1a4c 100644 --- a/internal/eventutil/eventcontent.go +++ b/internal/eventutil/eventcontent.go @@ -37,7 +37,7 @@ type CanonicalAlias struct { // if they have not been specified. // http://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-power-levels // https://github.com/matrix-org/synapse/blob/v0.19.2/synapse/handlers/room.py#L294 -func InitialPowerLevelsContent(roomCreator string) (c gomatrixserverlib.PowerLevelContent) { +func InitialPowerLevelsContent(roomVersion gomatrixserverlib.IRoomVersion, roomCreator string) (c gomatrixserverlib.PowerLevelContent) { c.Defaults() c.Events = map[string]int64{ "m.room.name": 50, @@ -49,7 +49,12 @@ func InitialPowerLevelsContent(roomCreator string) (c gomatrixserverlib.PowerLev "m.room.encryption": 100, "m.room.server_acl": 100, } - c.Users = map[string]int64{roomCreator: 100} + c.Users = map[string]int64{} + if roomVersion.PrivilegedCreators() { + c.Events["m.room.tombstone"] = 150 + } else { + c.Users[roomCreator] = 100 + } return c } diff --git a/internal/eventutil/events.go b/internal/eventutil/events.go index ac4481a2..958999ee 100644 --- a/internal/eventutil/events.go +++ b/internal/eventutil/events.go @@ -67,16 +67,24 @@ func BuildEvent( identity *fclient.SigningIdentity, evTime time.Time, eventsNeeded *gomatrixserverlib.StateNeeded, queryRes *api.QueryLatestEventsAndStateResponse, ) (*types.HeaderedEvent, error) { - if err := addPrevEventsToEvent(proto, eventsNeeded, queryRes); err != nil { - return nil, err - } - verImpl, err := gomatrixserverlib.GetRoomVersion(queryRes.RoomVersion) if err != nil { return nil, err } + proto.Version = verImpl + if err = addPrevEventsToEvent(proto, eventsNeeded, queryRes); err != nil { + return nil, err + } + builder := verImpl.NewEventBuilderFromProtoEvent(proto) + if verImpl.DomainlessRoomIDs() && builder.RoomID != "" && proto.Type == spec.MRoomCreate && proto.StateKey != nil && *proto.StateKey == "" { + return nil, gomatrixserverlib.EventValidationError{ + Message: "cannot resend m.room.create event", + Code: 400, + } + } + event, err := builder.Build( evTime, identity.ServerName, identity.KeyID, identity.PrivateKey, @@ -136,8 +144,22 @@ func addPrevEventsToEvent( if err != nil { return fmt.Errorf("eventsNeeded.AuthEventReferences: %w", err) } + var authEventIDs []string + if builder.Version.DomainlessRoomIDs() && len(builder.RoomID) > 0 { + // the room ID is the create event so we shouldn't set it in auth_events + authEventIDs = make([]string, 0, len(refs)) + createEventID := fmt.Sprintf("$%s", builder.RoomID[1:]) + for _, id := range refs { + if id == createEventID { + continue + } + authEventIDs = append(authEventIDs, id) + } + } else { + authEventIDs = refs + } - builder.AuthEvents, builder.PrevEvents = truncateAuthAndPrevEvents(refs, queryRes.LatestEvents) + builder.AuthEvents, builder.PrevEvents = truncateAuthAndPrevEvents(authEventIDs, queryRes.LatestEvents) return nil } diff --git a/roomserver/api/api.go b/roomserver/api/api.go index 9493995e..35f1d0b6 100644 --- a/roomserver/api/api.go +++ b/roomserver/api/api.go @@ -243,7 +243,7 @@ type ClientRoomserverAPI interface { 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) + PerformRoomUpgrade(ctx context.Context, roomID string, userID spec.UserID, roomVersion gomatrixserverlib.RoomVersion, additionalCreators []string) (newRoomID string, err error) PerformAdminEvacuateRoom(ctx context.Context, roomID string) (affected []string, err error) PerformAdminEvacuateUser(ctx context.Context, userID string) (affected []string, err error) PerformAdminPurgeRoom(ctx context.Context, roomID string) error diff --git a/roomserver/api/input.go b/roomserver/api/input.go index 748e3ac7..06dd5812 100644 --- a/roomserver/api/input.go +++ b/roomserver/api/input.go @@ -15,6 +15,9 @@ import ( "github.com/matrix-org/gomatrixserverlib/spec" ) +// for detecting rejected events and returning 403 instead of 500ing +const InputWasRejected = "InputWasRejected" + type Kind int const ( @@ -108,5 +111,5 @@ func (r *InputRoomEventsResponse) Err() error { Message: r.ErrMsg, } } - return fmt.Errorf("InputRoomEventsResponse: %s", r.ErrMsg) + return fmt.Errorf(r.ErrMsg) } diff --git a/roomserver/api/perform.go b/roomserver/api/perform.go index e448dcfa..e945cfca 100644 --- a/roomserver/api/perform.go +++ b/roomserver/api/perform.go @@ -17,6 +17,7 @@ type PerformCreateRoomRequest struct { Topic string StatePreset string CreationContent json.RawMessage + CreateEvent json.RawMessage InitialState []gomatrixserverlib.FledglingEvent RoomAliasName string RoomVersion gomatrixserverlib.RoomVersion diff --git a/roomserver/api/wrapper.go b/roomserver/api/wrapper.go index 7023e2c2..14115639 100644 --- a/roomserver/api/wrapper.go +++ b/roomserver/api/wrapper.go @@ -8,6 +8,10 @@ package api import ( "context" + "encoding/json" + "fmt" + "net/http" + "time" "github.com/element-hq/dendrite/roomserver/types" "github.com/matrix-org/gomatrixserverlib" @@ -216,3 +220,117 @@ func PopulatePublicRooms(ctx context.Context, roomIDs []string, rsAPI QueryBulkS } return chunk, nil } + +func GenerateCreateContent(ctx context.Context, roomVer gomatrixserverlib.RoomVersion, senderID string, createContentJSON json.RawMessage, additionalCreators []string) (map[string]any, error) { + createContent := map[string]any{} + if len(createContentJSON) > 0 { + if err := json.Unmarshal(createContentJSON, &createContent); err != nil { + return nil, fmt.Errorf("invalid create content: %s", err) + } + } + // TODO: Maybe, at some point, GMSL should return the events to create, so we can define the version + // entirely there. + switch roomVer { + case gomatrixserverlib.RoomVersionV11: + fallthrough + case gomatrixserverlib.RoomVersionV12: + // RoomVersionV11 removed the creator field from the create content: https://github.com/matrix-org/matrix-spec-proposals/pull/2175 + default: + createContent["creator"] = senderID + } + createContent["room_version"] = string(roomVer) + + verImpl := gomatrixserverlib.MustGetRoomVersion(roomVer) + + if verImpl.PrivilegedCreators() { + var finalAdditionalCreators []string + creatorsSet := make(map[string]struct{}) + var unverifiedCreators []string + unverifiedCreators = append(unverifiedCreators, additionalCreators...) + // they get added to any additional creators specified already + existingAdditionalCreators, ok := createContent["additional_creators"].([]any) + if ok { + for _, add := range existingAdditionalCreators { + addStr, ok := add.(string) + if ok { + unverifiedCreators = append(unverifiedCreators, addStr) + } + } + } + + for _, add := range unverifiedCreators { + if _, exists := creatorsSet[add]; exists { + continue + } + _, err := spec.NewUserID(add, true) + if err != nil { + return nil, fmt.Errorf("invalid additional creator: '%s': %s", add, err) + } + finalAdditionalCreators = append(finalAdditionalCreators, add) + creatorsSet[add] = struct{}{} + } + if len(finalAdditionalCreators) > 0 { + createContent["additional_creators"] = finalAdditionalCreators + } + } + + return createContent, nil +} + +func GeneratePDU( + ctx context.Context, verImpl gomatrixserverlib.IRoomVersion, e gomatrixserverlib.FledglingEvent, authEvents *gomatrixserverlib.AuthEvents, depth int, prevEventID string, + identity *fclient.SigningIdentity, timestamp time.Time, senderID, roomID string, queryer QuerySenderIDAPI, +) (gomatrixserverlib.PDU, *util.JSONResponse) { + builder := verImpl.NewEventBuilderFromProtoEvent(&gomatrixserverlib.ProtoEvent{ + SenderID: senderID, + 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 nil, &util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: spec.InternalServerError{}, + } + } + if prevEventID != "" { + builder.PrevEvents = []string{prevEventID} + } + var ev gomatrixserverlib.PDU + if err = builder.AddAuthEvents(authEvents); err != nil { + util.GetLogger(ctx).WithError(err).Error("AddAuthEvents failed") + return nil, &util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: spec.InternalServerError{}, + } + } + ev, err = builder.Build(timestamp, identity.ServerName, identity.KeyID, identity.PrivateKey) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("buildEvent failed") + return nil, &util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: spec.InternalServerError{}, + } + } + + if err = gomatrixserverlib.Allowed(ev, authEvents, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) { + return queryer.QueryUserIDForSender(ctx, roomID, senderID) + }); err != nil { + util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.Allowed failed") + validationErr, ok := err.(*gomatrixserverlib.EventValidationError) + if ok { + return nil, &util.JSONResponse{ + Code: validationErr.Code, + JSON: spec.Forbidden(err.Error()), + } + } + return nil, &util.JSONResponse{ + Code: http.StatusForbidden, + JSON: spec.Forbidden(err.Error()), + } + } + return ev, nil +} diff --git a/roomserver/internal/alias.go b/roomserver/internal/alias.go index b66cd09a..c7e2283a 100644 --- a/roomserver/internal/alias.go +++ b/roomserver/internal/alias.go @@ -11,6 +11,7 @@ import ( "database/sql" "errors" "fmt" + "slices" "time" asAPI "github.com/element-hq/dendrite/appservice/api" @@ -134,7 +135,7 @@ func (r *RoomserverInternalAPI) RemoveRoomAlias(ctx context.Context, senderID sp } if spec.SenderID(creatorID) != senderID { - var plEvent *types.HeaderedEvent + var createEvent, plEvent *types.HeaderedEvent var pls *gomatrixserverlib.PowerLevelContent plEvent, err = r.DB.GetStateEvent(ctx, roomID, spec.MRoomPowerLevels, "") @@ -147,7 +148,14 @@ func (r *RoomserverInternalAPI) RemoveRoomAlias(ctx context.Context, senderID sp return true, false, fmt.Errorf("plEvent.PowerLevels: %w", err) } - if pls.UserLevel(senderID) < pls.EventLevel(spec.MRoomCanonicalAlias, true) { + createEvent, err = r.DB.GetStateEvent(ctx, roomID, spec.MRoomCreate, "") + if err != nil { + return true, false, fmt.Errorf("r.DB.GetStateEvent: %w", err) + } + isPrivilegedCreator := gomatrixserverlib.MustGetRoomVersion(createEvent.Version()).PrivilegedCreators() && + slices.Contains(gomatrixserverlib.CreatorsFromCreateEvent(createEvent), string(senderID)) + + if !isPrivilegedCreator && pls.UserLevel(senderID) < pls.EventLevel(spec.MRoomCanonicalAlias, true) { return true, false, nil } } diff --git a/roomserver/internal/input/input.go b/roomserver/internal/input/input.go index d07eaf41..f80cdfb9 100644 --- a/roomserver/internal/input/input.go +++ b/roomserver/internal/input/input.go @@ -271,7 +271,11 @@ func (w *worker) _next() { }) msgs, err := w.subscription.Fetch(1, nats.Context(ctx)) switch err { - case nil: + case nil, nats.ErrTimeout, context.DeadlineExceeded, context.Canceled: + // Is the server shutting down? If so, stop processing. + if w.r.ProcessContext.Context().Err() != nil { + return + } // Make sure that once we're done here, we queue up another call // to _next in the inbox. defer w.Act(nil, w._next) @@ -281,7 +285,6 @@ func (w *worker) _next() { if len(msgs) != 1 { return } - case context.DeadlineExceeded, context.Canceled: // The context exceeded, so we've been waiting for more than a // minute for activity in this room. At this point we will shut @@ -295,7 +298,11 @@ func (w *worker) _next() { w.Act(nil, w._next) return } - + case nats.ErrConsumerDeleted, nats.ErrConsumerNotFound: + // The consumer is gone, therefore it's reached the inactivity + // threshold. Clean up and stop processing at this point, if a + // new event comes in for this room then the ordered consumer + // over the entire stream will recreate this anyway. if err = w.subscription.Unsubscribe(); err != nil { logrus.WithError(err).Errorf("Failed to unsubscribe to stream for room %q", w.roomID) } @@ -345,6 +352,7 @@ func (w *worker) _next() { // a string, because we might want to return that to the caller if // it was a synchronous request. var errString string + wasRejected := false if err = w.r.processRoomEvent( w.r.ProcessContext.Context(), spec.ServerName(msg.Header.Get("virtual_host")), @@ -358,6 +366,7 @@ func (w *worker) _next() { "event_id": inputRoomEvent.Event.EventID(), "type": inputRoomEvent.Event.Type(), }).Warn("Roomserver rejected event") + wasRejected = true default: if !errors.Is(err, context.DeadlineExceeded) && !errors.Is(err, context.Canceled) { w.sentryHub.CaptureException(err) @@ -379,6 +388,10 @@ func (w *worker) _next() { _ = msg.AckSync() } + if wasRejected { + errString = api.InputWasRejected + } + // If it was a synchronous input request then the "sync" field // will be present in the message. That means that someone is // waiting for a response. The temporary inbox name is present in diff --git a/roomserver/internal/perform/perform_create_room.go b/roomserver/internal/perform/perform_create_room.go index 020e7495..00bdff60 100644 --- a/roomserver/internal/perform/perform_create_room.go +++ b/roomserver/internal/perform/perform_create_room.go @@ -39,6 +39,7 @@ type Creator struct { // PerformCreateRoom handles all the steps necessary to create a new room. // nolint: gocyclo func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roomID spec.RoomID, createRequest *api.PerformCreateRoomRequest) (string, *util.JSONResponse) { + // Make sure we know the room version verImpl, err := gomatrixserverlib.GetRoomVersion(createRequest.RoomVersion) if err != nil { return "", &util.JSONResponse{ @@ -47,17 +48,7 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo } } - createContent := map[string]interface{}{} - if len(createRequest.CreationContent) > 0 { - if err = json.Unmarshal(createRequest.CreationContent, &createContent); err != nil { - util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for creation_content failed") - return "", &util.JSONResponse{ - Code: http.StatusBadRequest, - JSON: spec.BadJSON("invalid create content"), - } - } - } - + // Allocate the room _, err = c.DB.AssignRoomNID(ctx, roomID, createRequest.RoomVersion) if err != nil { util.GetLogger(ctx).WithError(err).Error("failed to assign roomNID") @@ -67,6 +58,7 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo } } + // Allocate the user var senderID spec.SenderID if createRequest.RoomVersion == gomatrixserverlib.RoomVersionPseudoIDs { // create user room key if needed @@ -83,17 +75,73 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo senderID = spec.SenderID(userID.String()) } - // TODO: Maybe, at some point, GMSL should return the events to create, so we can define the version - // entirely there. - switch createRequest.RoomVersion { - case gomatrixserverlib.RoomVersionV11: - // RoomVersionV11 removed the creator field from the create content: https://github.com/matrix-org/matrix-spec-proposals/pull/2175 - default: - createContent["creator"] = senderID + // get the signing identity + identity, err := c.Cfg.Matrix.SigningIdentityFor(userID.Domain()) // we MUST use the server signing mxid_mapping + if err != nil { + logrus.WithError(err).WithField("domain", userID.Domain()).Error("unable to find signing identity for domain") + return "", &util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: spec.InternalServerError{}, + } } - createContent["room_version"] = createRequest.RoomVersion - powerLevelContent := eventutil.InitialPowerLevelsContent(string(senderID)) + // Make the create event if we need to + var ( + createEvent gomatrixserverlib.PDU + jsonErr *util.JSONResponse + ) + authEvents, _ := gomatrixserverlib.NewAuthEvents(nil) + if createRequest.CreateEvent != nil { + createEvent, err = verImpl.NewEventFromTrustedJSON(createRequest.CreateEvent, false) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.NewEventFromTrustedJSON failed to verify create event") + return "", &util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: spec.InternalServerError{}, + } + } + if err = authEvents.AddEvent(createEvent); err != nil { + util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.AuthEvents.AddEvent failed") + return "", &util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: spec.InternalServerError{}, + } + } + } else { + var additionalCreators []string + if createRequest.StatePreset == spec.PresetTrustedPrivateChat { + additionalCreators = createRequest.InvitedUsers + } + createContent, contentErr := api.GenerateCreateContent(ctx, createRequest.RoomVersion, string(senderID), createRequest.CreationContent, additionalCreators) + if contentErr != nil { + util.GetLogger(ctx).WithError(contentErr).Error("GenerateCreateContent failed") + return "", &util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: spec.BadJSON("invalid create content"), + } + } + createEvent, jsonErr = api.GeneratePDU( + ctx, verImpl, + gomatrixserverlib.FledglingEvent{ + Type: spec.MRoomCreate, + Content: createContent, + }, + authEvents, 1, "", identity, createRequest.EventTime, string(senderID), roomID.String(), c.RSAPI, + ) + if jsonErr != nil { + util.GetLogger(ctx).WithError(err).Error("Failed to make the create event") + return "", jsonErr + } + if err = authEvents.AddEvent(createEvent); err != nil { + util.GetLogger(ctx).WithError(err).Error("authEvents.AddEvent failed") + return "", &util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: spec.InternalServerError{}, + } + } + } + + powerLevelContent := eventutil.InitialPowerLevelsContent(verImpl, string(senderID)) joinRuleContent := gomatrixserverlib.JoinRuleContent{ JoinRule: spec.Invite, } @@ -122,8 +170,10 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo case spec.PresetTrustedPrivateChat: joinRuleContent.JoinRule = spec.Invite historyVisibilityContent.HistoryVisibility = historyVisibilityShared - for _, invitee := range createRequest.InvitedUsers { - powerLevelContent.Users[invitee] = 100 + if !verImpl.PrivilegedCreators() { + for _, invitee := range createRequest.InvitedUsers { + powerLevelContent.Users[invitee] = 100 + } } guestsCanJoin = true case spec.PresetPublicChat: @@ -131,10 +181,6 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo historyVisibilityContent.HistoryVisibility = historyVisibilityShared } - createEvent := gomatrixserverlib.FledglingEvent{ - Type: spec.MRoomCreate, - Content: createContent, - } powerLevelEvent := gomatrixserverlib.FledglingEvent{ Type: spec.MRoomPowerLevels, Content: powerLevelContent, @@ -158,16 +204,6 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo AvatarURL: createRequest.UserAvatarURL, } - // get the signing identity - identity, err := c.Cfg.Matrix.SigningIdentityFor(userID.Domain()) // we MUST use the server signing mxid_mapping - if err != nil { - logrus.WithError(err).WithField("domain", userID.Domain()).Error("unable to find signing identity for domain") - return "", &util.JSONResponse{ - Code: http.StatusInternalServerError, - JSON: spec.InternalServerError{}, - } - } - // If we are creating a room with pseudo IDs, create and sign the MXIDMapping if createRequest.RoomVersion == gomatrixserverlib.RoomVersionPseudoIDs { var pseudoIDKey ed25519.PrivateKey @@ -279,7 +315,6 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo switch createRequest.InitialState[i].Type { case spec.MRoomCreate: continue - case spec.MRoomPowerLevels: powerLevelEvent = createRequest.InitialState[i] @@ -321,7 +356,8 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo // harder to reason about, hence sticking to a strict static ordering. // TODO: Synapse has txn/token ID on each event. Do we need to do this here? eventsToMake := []gomatrixserverlib.FledglingEvent{ - createEvent, membershipEvent, powerLevelEvent, joinRuleEvent, historyVisibilityEvent, + // we made the create event already hence it isn't here. + membershipEvent, powerLevelEvent, joinRuleEvent, historyVisibilityEvent, } if guestAccessEvent != nil { eventsToMake = append(eventsToMake, *guestAccessEvent) @@ -342,61 +378,19 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo // TODO: invite events // TODO: 3pid invite events - var builtEvents []*types.HeaderedEvent - authEvents, _ := gomatrixserverlib.NewAuthEvents(nil) - if err != nil { - util.GetLogger(ctx).WithError(err).Error("rsapi.QuerySenderIDForUser failed") - return "", &util.JSONResponse{ - Code: http.StatusInternalServerError, - JSON: spec.InternalServerError{}, - } + builtEvents := []*types.HeaderedEvent{ + {PDU: createEvent}, } for i, e := range eventsToMake { - depth := i + 1 // depth starts at 1 + depth := i + 2 // depth starts at 2 since we made the create event already - builder := verImpl.NewEventBuilderFromProtoEvent(&gomatrixserverlib.ProtoEvent{ - SenderID: string(senderID), - RoomID: roomID.String(), - Type: e.Type, - StateKey: &e.StateKey, - Depth: int64(depth), - }) - err = builder.SetContent(e.Content) - if err != nil { - util.GetLogger(ctx).WithError(err).Error("builder.SetContent failed") - return "", &util.JSONResponse{ - Code: http.StatusInternalServerError, - JSON: spec.InternalServerError{}, - } - } - if i > 0 { - builder.PrevEvents = []string{builtEvents[i-1].EventID()} - } - var ev gomatrixserverlib.PDU - if err = builder.AddAuthEvents(authEvents); err != nil { - util.GetLogger(ctx).WithError(err).Error("AddAuthEvents failed") - return "", &util.JSONResponse{ - Code: http.StatusInternalServerError, - JSON: spec.InternalServerError{}, - } - } - ev, err = builder.Build(createRequest.EventTime, identity.ServerName, identity.KeyID, identity.PrivateKey) - if err != nil { - util.GetLogger(ctx).WithError(err).Error("buildEvent failed") - return "", &util.JSONResponse{ - Code: http.StatusInternalServerError, - JSON: spec.InternalServerError{}, - } - } - - if err = gomatrixserverlib.Allowed(ev, authEvents, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) { - return c.RSAPI.QueryUserIDForSender(ctx, roomID, senderID) - }); err != nil { - util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.Allowed failed") - return "", &util.JSONResponse{ - Code: http.StatusInternalServerError, - JSON: spec.InternalServerError{}, - } + ev, jsonErr := api.GeneratePDU( + ctx, verImpl, e, + authEvents, depth, builtEvents[len(builtEvents)-1].EventID(), + identity, createRequest.EventTime, string(senderID), roomID.String(), c.RSAPI, + ) + if jsonErr != nil { + return "", jsonErr } // Add the event to the list of auth events diff --git a/roomserver/internal/perform/perform_join.go b/roomserver/internal/perform/perform_join.go index c58d48ff..a1b54e3f 100644 --- a/roomserver/internal/perform/perform_join.go +++ b/roomserver/internal/perform/perform_join.go @@ -156,19 +156,11 @@ func (r *Joiner) performJoinRoomByID( } } - // Get the domain part of the room ID. roomID, err := spec.NewRoomID(req.RoomIDOrAlias) if err != nil { return "", "", rsAPI.ErrInvalidID{Err: fmt.Errorf("room ID %q is invalid: %w", req.RoomIDOrAlias, err)} } - // If the server name in the room ID isn't ours then it's a - // possible candidate for finding the room via federation. Add - // it to the list of servers to try. - if !r.Cfg.Matrix.IsLocalServerName(roomID.Domain()) { - req.ServerNames = append(req.ServerNames, roomID.Domain()) - } - // Force a federated join if we aren't in the room and we've been // given some server names to try joining by. inRoomReq := &rsAPI.QueryServerJoinedToRoomRequest{ diff --git a/roomserver/internal/perform/perform_upgrade.go b/roomserver/internal/perform/perform_upgrade.go index 8b28e78b..8fbed020 100644 --- a/roomserver/internal/perform/perform_upgrade.go +++ b/roomserver/internal/perform/perform_upgrade.go @@ -10,6 +10,7 @@ import ( "context" "encoding/json" "fmt" + "slices" "time" "github.com/element-hq/dendrite/internal/eventutil" @@ -30,14 +31,15 @@ type Upgrader struct { // PerformRoomUpgrade upgrades a room from one version to another func (r *Upgrader) PerformRoomUpgrade( ctx context.Context, - roomID string, userID spec.UserID, roomVersion gomatrixserverlib.RoomVersion, + roomID string, userID spec.UserID, roomVersion gomatrixserverlib.RoomVersion, additionalCreators []string, ) (newRoomID string, err error) { - return r.performRoomUpgrade(ctx, roomID, userID, roomVersion) + return r.performRoomUpgrade(ctx, roomID, userID, roomVersion, additionalCreators) } +// nolint:gocyclo func (r *Upgrader) performRoomUpgrade( ctx context.Context, - roomID string, userID spec.UserID, roomVersion gomatrixserverlib.RoomVersion, + roomID string, userID spec.UserID, roomVersion gomatrixserverlib.RoomVersion, additionalCreators []string, ) (string, error) { evTime := time.Now() @@ -64,35 +66,110 @@ func (r *Upgrader) performRoomUpgrade( return "", api.ErrNotAllowed{Err: fmt.Errorf("You don't have permission to upgrade the room, power level too low.")} } - // TODO (#267): Check room ID doesn't clash with an existing one, and we - // probably shouldn't be using pseudo-random strings, maybe GUIDs? - newRoomID := fmt.Sprintf("!%s:%s", util.RandomString(16), userID.Domain()) - // Get the existing room state for the old room. oldRoomReq := &api.QueryLatestEventsAndStateRequest{ RoomID: roomID, } oldRoomRes := &api.QueryLatestEventsAndStateResponse{} - if err := r.URSAPI.QueryLatestEventsAndState(ctx, oldRoomReq, oldRoomRes); err != nil { + if err = r.URSAPI.QueryLatestEventsAndState(ctx, oldRoomReq, oldRoomRes); err != nil { return "", fmt.Errorf("Failed to get latest state: %s", err) } - - // Make the tombstone event - tombstoneEvent, pErr := r.makeTombstoneEvent(ctx, evTime, *senderID, userID.Domain(), roomID, newRoomID) - if pErr != nil { - return "", pErr + var oldCreateEvent *types.HeaderedEvent + for _, ev := range oldRoomRes.StateEvents { + if ev.Type() == spec.MRoomCreate && ev.StateKeyEquals("") { + oldCreateEvent = ev + break + } } + // Make the create event and calculate the new room ID. + var newRoomID string + newRoomVerImpl := gomatrixserverlib.MustGetRoomVersion(roomVersion) + var tombstoneEvent *types.HeaderedEvent + var newCreateEvent gomatrixserverlib.PDU + var pErr error + if !newRoomVerImpl.DomainlessRoomIDs() { + // TODO (#267): Check room ID doesn't clash with an existing one, and we + // probably shouldn't be using pseudo-random strings, maybe GUIDs? + newRoomID = fmt.Sprintf("!%s:%s", util.RandomString(16), userID.Domain()) + + // Make the tombstone event + tombstoneEvent, pErr = r.makeTombstoneEvent(ctx, evTime, *senderID, userID.Domain(), roomID, newRoomID) + if pErr != nil { + return "", pErr + } + } + content := struct { + Federate *bool `json:"m.federate,omitempty"` + Type string `json:"type,omitempty"` + Predecessor struct { + RoomID string `json:"room_id"` + EventID string `json:"event_id,omitempty"` + } `json:"predecessor"` + }{} + // keep existing values in old room e.g type/m.federate + if err = json.Unmarshal(oldCreateEvent.Content(), &content); err != nil { + return "", fmt.Errorf("failed to copy old create event content to new create event: %s", err) + } + content.Predecessor.RoomID = roomID + content.Predecessor.EventID = "" + if tombstoneEvent != nil { + content.Predecessor.EventID = tombstoneEvent.EventID() + } + contentJSON, err := json.Marshal(content) + if err != nil { + return "", fmt.Errorf("Failed to make content for new create event: %s", err) + } + // make the create event up-front so the roomserver can calculate the room NID to store. + createContent, err := api.GenerateCreateContent(ctx, roomVersion, userID.String(), contentJSON, additionalCreators) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("GenerateCreateContent failed") + return "", fmt.Errorf("failed to GenerateCreateContent") + } + authEvents, _ := gomatrixserverlib.NewAuthEvents(nil) + identity, err := r.Cfg.Matrix.SigningIdentityFor(userID.Domain()) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("Failed to get signing identity") + return "", fmt.Errorf("No SigningIdentityFor domain %s", userID.Domain()) + } + createEvent, jsonErr := api.GeneratePDU( + ctx, gomatrixserverlib.MustGetRoomVersion(roomVersion), + gomatrixserverlib.FledglingEvent{ + Type: spec.MRoomCreate, + Content: createContent, + }, + // newRoomID will be empty for domainless rooms + authEvents, 1, "", identity, evTime, userID.String(), newRoomID, r.URSAPI, + ) + if jsonErr != nil { + util.GetLogger(ctx).Error("Failed to make the create event") + return "", fmt.Errorf("failed to create new create event PDU") + } + newCreateEvent = createEvent + if newRoomVerImpl.DomainlessRoomIDs() { + newRoomID = newCreateEvent.RoomID().String() + } + + if tombstoneEvent == nil { + // Make the tombstone event + tombstoneEvent, pErr = r.makeTombstoneEvent(ctx, evTime, *senderID, userID.Domain(), roomID, newRoomID) + if pErr != nil { + return "", pErr + } + } + + creators := gomatrixserverlib.CreatorsFromCreateEvent(newCreateEvent) + // Generate the initial events we need to send into the new room. This includes copied state events and bans // as well as the power level events needed to set up the room - eventsToMake, pErr := r.generateInitialEvents(ctx, oldRoomRes, *senderID, roomID, roomVersion, tombstoneEvent) + eventsToMake, pErr := r.generateInitialEvents(ctx, oldRoomRes, *senderID, roomID, roomVersion, creators) if pErr != nil { return "", pErr } // Send the setup events to the new room - if pErr = r.sendInitialEvents(ctx, evTime, *senderID, userID.Domain(), newRoomID, roomVersion, eventsToMake); pErr != nil { - return "", pErr + if pErr = r.sendInitialEvents(ctx, evTime, *senderID, userID.Domain(), newRoomID, roomVersion, newCreateEvent, eventsToMake); pErr != nil { + return "", fmt.Errorf("sendInitialEvents: %s", pErr) } // 5. Send the tombstone event to the old room @@ -296,13 +373,25 @@ func (r *Upgrader) userIsAuthorized(ctx context.Context, senderID spec.SenderID, if err != nil { return false } + createEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{ + EventType: spec.MRoomCreate, + StateKey: "", + }) + if gomatrixserverlib.MustGetRoomVersion(createEvent.Version()).PrivilegedCreators() && + slices.Contains(gomatrixserverlib.CreatorsFromCreateEvent(createEvent), string(senderID)) { + return true + } // Check for power level required to send tombstone event (marks the current room as obsolete), // if not found, use the StateDefault power level return pl.UserLevel(senderID) >= pl.EventLevel("m.room.tombstone", true) } +// Return the events to create AFTER the new create event // nolint:gocyclo -func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.QueryLatestEventsAndStateResponse, senderID spec.SenderID, roomID string, newVersion gomatrixserverlib.RoomVersion, tombstoneEvent *types.HeaderedEvent) ([]gomatrixserverlib.FledglingEvent, error) { +func (r *Upgrader) generateInitialEvents( + ctx context.Context, oldRoom *api.QueryLatestEventsAndStateResponse, senderID spec.SenderID, _ string, newVersion gomatrixserverlib.RoomVersion, + creators []string) ([]gomatrixserverlib.FledglingEvent, error) { + state := make(map[gomatrixserverlib.StateKeyTuple]*types.HeaderedEvent, len(oldRoom.StateEvents)) for _, event := range oldRoom.StateEvents { if event.StateKey() == nil { @@ -350,37 +439,10 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query } } - oldCreateEvent := state[gomatrixserverlib.StateKeyTuple{EventType: spec.MRoomCreate, StateKey: ""}] oldMembershipEvent := state[gomatrixserverlib.StateKeyTuple{EventType: spec.MRoomMember, StateKey: string(senderID)}] oldPowerLevelsEvent := state[gomatrixserverlib.StateKeyTuple{EventType: spec.MRoomPowerLevels, StateKey: ""}] oldJoinRulesEvent := state[gomatrixserverlib.StateKeyTuple{EventType: spec.MRoomJoinRules, StateKey: ""}] - // Create the new room create event. Using a map here instead of CreateContent - // means that we preserve any other interesting fields that might be present - // in the create event (such as for the room types MSC). - newCreateContent := map[string]interface{}{} - _ = json.Unmarshal(oldCreateEvent.Content(), &newCreateContent) - - switch newVersion { - case gomatrixserverlib.RoomVersionV11: - // RoomVersionV11 removed the creator field from the create content: https://github.com/matrix-org/matrix-spec-proposals/pull/2175 - // So if we are upgrading from pre v11, we need to remove the field. - delete(newCreateContent, "creator") - default: - newCreateContent["creator"] = senderID - } - - newCreateContent["room_version"] = newVersion - newCreateContent["predecessor"] = gomatrixserverlib.PreviousRoom{ - EventID: tombstoneEvent.EventID(), - RoomID: roomID, - } - newCreateEvent := gomatrixserverlib.FledglingEvent{ - Type: spec.MRoomCreate, - StateKey: "", - Content: newCreateContent, - } - // Now create the new membership event. Same rules apply as above, so // that we preserve fields we don't otherwise know about. We'll always // set the membership to join though, because that is necessary to auth @@ -405,7 +467,9 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query return nil, fmt.Errorf("Power level event content was invalid") } - tempPowerLevelsEvent, powerLevelsOverridden := createTemporaryPowerLevels(powerLevelContent, senderID) + verImpl := gomatrixserverlib.MustGetRoomVersion(newVersion) + + tempPowerLevelsEvent, powerLevelsOverridden := createTemporaryPowerLevels(verImpl, powerLevelContent, senderID, creators) // Now do the join rules event, same as the create and membership // events. We'll set a sane default of "invite" so that if the @@ -423,7 +487,7 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query eventsToMake := make([]gomatrixserverlib.FledglingEvent, 0, len(state)) eventsToMake = append( - eventsToMake, newCreateEvent, newMembershipEvent, + eventsToMake, newMembershipEvent, tempPowerLevelsEvent, newJoinRulesEvent, ) @@ -467,12 +531,16 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query return eventsToMake, nil } -func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, senderID spec.SenderID, userDomain spec.ServerName, newRoomID string, newVersion gomatrixserverlib.RoomVersion, eventsToMake []gomatrixserverlib.FledglingEvent) error { +func (r *Upgrader) sendInitialEvents( + ctx context.Context, evTime time.Time, senderID spec.SenderID, userDomain spec.ServerName, newRoomID string, + newVersion gomatrixserverlib.RoomVersion, newCreateEvent gomatrixserverlib.PDU, eventsToMake []gomatrixserverlib.FledglingEvent) error { + var err error var builtEvents []*types.HeaderedEvent - authEvents, _ := gomatrixserverlib.NewAuthEvents(nil) + builtEvents = append(builtEvents, &types.HeaderedEvent{PDU: newCreateEvent}) + authEvents, _ := gomatrixserverlib.NewAuthEvents([]gomatrixserverlib.PDU{newCreateEvent}) for i, e := range eventsToMake { - depth := i + 1 // depth starts at 1 + depth := i + 2 // depth starts at 2 since we made the create event already. proto := gomatrixserverlib.ProtoEvent{ SenderID: string(senderID), @@ -485,9 +553,7 @@ func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, send if err != nil { return fmt.Errorf("failed to set content of new %q event: %w", proto.Type, err) } - if i > 0 { - proto.PrevEvents = []string{builtEvents[i-1].EventID()} - } + proto.PrevEvents = []string{builtEvents[i].EventID()} var verImpl gomatrixserverlib.IRoomVersion verImpl, err = gomatrixserverlib.GetRoomVersion(newVersion) @@ -503,13 +569,12 @@ func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, send event, err = builder.Build(evTime, userDomain, r.Cfg.Matrix.KeyID, r.Cfg.Matrix.PrivateKey) if err != nil { return fmt.Errorf("failed to build new %q event: %w", builder.Type, err) - } if err = gomatrixserverlib.Allowed(event, authEvents, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) { return r.URSAPI.QueryUserIDForSender(ctx, roomID, senderID) }); err != nil { - return fmt.Errorf("Failed to auth new %q event: %w", builder.Type, err) + return fmt.Errorf("Failed to auth new initial %q event: %w", builder.Type, err) } // Add the event to the list of auth events @@ -599,7 +664,7 @@ func (r *Upgrader) makeHeaderedEvent(ctx context.Context, evTime time.Time, send return headeredEvent, nil } -func createTemporaryPowerLevels(powerLevelContent *gomatrixserverlib.PowerLevelContent, senderID spec.SenderID) (gomatrixserverlib.FledglingEvent, bool) { +func createTemporaryPowerLevels(roomVersion gomatrixserverlib.IRoomVersion, powerLevelContent *gomatrixserverlib.PowerLevelContent, senderID spec.SenderID, creators []string) (gomatrixserverlib.FledglingEvent, bool) { // Work out what power level we need in order to be able to send events // of all types into the room. neededPowerLevel := powerLevelContent.StateDefault @@ -619,14 +684,20 @@ func createTemporaryPowerLevels(powerLevelContent *gomatrixserverlib.PowerLevelC // so that we can modify them without modifying the original. tempPowerLevelContent.Users = make(map[string]int64, len(powerLevelContent.Users)) for key, value := range powerLevelContent.Users { + if roomVersion.PrivilegedCreators() && slices.Contains(creators, key) { + continue // don't set the creator in the users map! + } tempPowerLevelContent.Users[key] = value } - // If the user who is upgrading the room doesn't already have sufficient - // power, then elevate their power levels. - if tempPowerLevelContent.UserLevel(senderID) < neededPowerLevel { - tempPowerLevelContent.Users[string(senderID)] = neededPowerLevel - powerLevelsOverridden = true + // the upgrader will be the creator so is guaranteed to have enough perms to do this. + if !roomVersion.PrivilegedCreators() { + // If the user who is upgrading the room doesn't already have sufficient + // power, then elevate their power levels. + if tempPowerLevelContent.UserLevel(senderID) < neededPowerLevel { + tempPowerLevelContent.Users[string(senderID)] = neededPowerLevel + powerLevelsOverridden = true + } } // Then return the temporary power levels event. diff --git a/roomserver/internal/query/query.go b/roomserver/internal/query/query.go index e5ee8f83..37de303b 100644 --- a/roomserver/internal/query/query.go +++ b/roomserver/internal/query/query.go @@ -13,7 +13,7 @@ import ( "errors" "fmt" - //"github.com/element-hq/dendrite/roomserver/internal" + // "github.com/element-hq/dendrite/roomserver/internal" "github.com/element-hq/dendrite/setup/config" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" @@ -747,7 +747,7 @@ func GetAuthChain( // from the database and the `eventsToFetch` will be updated with any new // events that we have learned about and need to find. When `eventsToFetch` // is eventually empty, we should have reached the end of the chain. - eventsToFetch := authEventIDs + eventsToFetch := append([]string{}, authEventIDs...) authEventsMap := make(map[string]gomatrixserverlib.PDU) for len(eventsToFetch) > 0 { @@ -779,7 +779,7 @@ func GetAuthChain( // We've now retrieved all of the events we can. Flatten them down into an // array and return them. - var authEvents []gomatrixserverlib.PDU + authEvents := make([]gomatrixserverlib.PDU, 0, len(authEventsMap)) for _, event := range authEventsMap { authEvents = append(authEvents, event) } diff --git a/roomserver/roomserver_test.go b/roomserver/roomserver_test.go index 48911d2b..659ad714 100644 --- a/roomserver/roomserver_test.go +++ b/roomserver/roomserver_test.go @@ -1075,7 +1075,7 @@ func TestUpgrade(t *testing.T) { if err != nil { t.Fatalf("upgrade userID is invalid") } - newRoomID, err := rsAPI.PerformRoomUpgrade(processCtx.Context(), roomID, *userID, rsAPI.DefaultRoomVersion()) + newRoomID, err := rsAPI.PerformRoomUpgrade(processCtx.Context(), roomID, *userID, rsAPI.DefaultRoomVersion(), nil) if err != nil && tc.wantNewRoom { t.Fatal(err) } diff --git a/roomserver/state/state.go b/roomserver/state/state.go index 9a7669a4..a5c84d1f 100644 --- a/roomserver/state/state.go +++ b/roomserver/state/state.go @@ -888,6 +888,8 @@ func (v *StateResolution) resolveConflicts( case gomatrixserverlib.StateResV1: return v.resolveConflictsV1(ctx, notConflicted, conflicted) case gomatrixserverlib.StateResV2: + fallthrough + case gomatrixserverlib.StateResV2_1: return v.resolveConflictsV2(ctx, notConflicted, conflicted) } return nil, fmt.Errorf("unsupported state resolution algorithm %v", stateResAlgo) diff --git a/roomserver/storage/shared/storage.go b/roomserver/storage/shared/storage.go index ac5f54ce..31c16c34 100644 --- a/roomserver/storage/shared/storage.go +++ b/roomserver/storage/shared/storage.go @@ -1052,6 +1052,7 @@ func (d *EventDatabase) MaybeRedactEvent( return err } + // TODO HYDRA: we need to load the create event here switch { case powerlevels.UserLevel(redactionEvent.SenderID()) >= powerlevels.Redact: // 1. The power level of the redaction event’s sender is greater than or equal to the redact level. diff --git a/syncapi/routing/messages.go b/syncapi/routing/messages.go index 8d1434ab..2fabb182 100644 --- a/syncapi/routing/messages.go +++ b/syncapi/routing/messages.go @@ -216,14 +216,6 @@ func OnIncomingMessagesRequest( // TODO: Implement filtering (#587) - // Check the room ID's format. - if _, _, err = gomatrixserverlib.SplitID('!', roomID); err != nil { - return util.JSONResponse{ - Code: http.StatusBadRequest, - JSON: spec.MissingParam("Bad room ID: " + err.Error()), - } - } - // If the user already left the room, grep events from before that if membershipResp.Membership == spec.Leave { var token types.TopologyToken diff --git a/syncapi/storage/postgres/account_data_table.go b/syncapi/storage/postgres/account_data_table.go index e4b558ff..c8feaa6a 100644 --- a/syncapi/storage/postgres/account_data_table.go +++ b/syncapi/storage/postgres/account_data_table.go @@ -92,6 +92,7 @@ func (s *accountDataStatements) SelectAccountDataInRange( accountDataEventFilter *synctypes.EventFilter, ) (data map[string][]string, pos types.StreamPosition, err error) { data = make(map[string][]string) + pos = r.Low() rows, err := sqlutil.TxStmt(txn, s.selectAccountDataInRangeStmt).QueryContext( ctx, userID, r.Low(), r.High(), @@ -122,7 +123,7 @@ func (s *accountDataStatements) SelectAccountDataInRange( pos = id } } - if pos == 0 { + if len(data) == 0 { pos = r.High() } return data, pos, rows.Err() diff --git a/syncapi/storage/sqlite3/account_data_table.go b/syncapi/storage/sqlite3/account_data_table.go index b98cf227..a84deb87 100644 --- a/syncapi/storage/sqlite3/account_data_table.go +++ b/syncapi/storage/sqlite3/account_data_table.go @@ -84,6 +84,8 @@ func (s *accountDataStatements) SelectAccountDataInRange( filter *synctypes.EventFilter, ) (data map[string][]string, pos types.StreamPosition, err error) { data = make(map[string][]string) + pos = r.Low() + stmt, params, err := prepareWithFilters( s.db, txn, selectAccountDataInRangeSQL, []interface{}{ @@ -119,7 +121,7 @@ func (s *accountDataStatements) SelectAccountDataInRange( pos = id } } - if pos == 0 { + if len(data) == 0 { pos = r.High() } return data, pos, rows.Err() diff --git a/test/room.go b/test/room.go index cfafaac1..dbe99a2e 100644 --- a/test/room.go +++ b/test/room.go @@ -94,7 +94,7 @@ func (r *Room) insertCreateEvents(t *testing.T) { t.Helper() var joinRule gomatrixserverlib.JoinRuleContent var hisVis gomatrixserverlib.HistoryVisibilityContent - plContent := eventutil.InitialPowerLevelsContent(r.creator.ID) + plContent := eventutil.InitialPowerLevelsContent(gomatrixserverlib.MustGetRoomVersion(r.Version), r.creator.ID) switch r.preset { case PresetTrustedPrivateChat: fallthrough diff --git a/userapi/consumers/roomserver.go b/userapi/consumers/roomserver.go index 3dbace2e..b33799dc 100644 --- a/userapi/consumers/roomserver.go +++ b/userapi/consumers/roomserver.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "slices" "strings" "sync" "time" @@ -727,25 +728,34 @@ func (rse *ruleSetEvalContext) HasPowerLevel(senderID spec.SenderID, levelKey st req := &rsapi.QueryLatestEventsAndStateRequest{ RoomID: rse.roomID, StateToFetch: []gomatrixserverlib.StateKeyTuple{ - {EventType: spec.MRoomPowerLevels}, + {EventType: spec.MRoomPowerLevels, StateKey: ""}, + {EventType: spec.MRoomCreate, StateKey: ""}, }, } var res rsapi.QueryLatestEventsAndStateResponse if err := rse.rsAPI.QueryLatestEventsAndState(rse.ctx, req, &res); err != nil { return false, err } - for _, ev := range res.StateEvents { - if ev.Type() != spec.MRoomPowerLevels { - continue + var createEvent, plEvent *rstypes.HeaderedEvent + for i, ev := range res.StateEvents { + if ev.Type() == spec.MRoomCreate { + createEvent = res.StateEvents[i] + } else if ev.Type() == spec.MRoomPowerLevels { + plEvent = res.StateEvents[i] } - - plc, err := gomatrixserverlib.NewPowerLevelContentFromEvent(ev.PDU) - if err != nil { - return false, err - } - return plc.UserLevel(senderID) >= plc.NotificationLevel(levelKey), nil } - return true, nil + verImpl := gomatrixserverlib.MustGetRoomVersion(createEvent.Version()) + if verImpl.PrivilegedCreators() && slices.Contains(gomatrixserverlib.CreatorsFromCreateEvent(createEvent), string(senderID)) { + return true, nil + } + if plEvent == nil { + return true, nil // unsure, but this is what we did before + } + plc, err := gomatrixserverlib.NewPowerLevelContentFromEvent(plEvent.PDU) + if err != nil { + return false, err + } + return plc.UserLevel(senderID) >= plc.NotificationLevel(levelKey), nil } // localPushDevices pushes to the configured devices of a local diff --git a/userapi/internal/user_api.go b/userapi/internal/user_api.go index 666e75f9..48fb441a 100644 --- a/userapi/internal/user_api.go +++ b/userapi/internal/user_api.go @@ -337,7 +337,7 @@ func (a *UserInternalAPI) PerformDeviceDeletion(ctx context.Context, req *api.Pe deleteReq := &api.PerformDeleteKeysRequest{ UserID: req.UserID, } - for _, keyID := range req.DeviceIDs { + for _, keyID := range deletedDeviceIDs { deleteReq.KeyIDs = append(deleteReq.KeyIDs, gomatrixserverlib.KeyID(keyID)) } deleteRes := &api.PerformDeleteKeysResponse{}