diff --git a/internal/mscs/msc2836/msc2836.go b/internal/mscs/msc2836/msc2836.go index b4e89c607..80b716ffc 100644 --- a/internal/mscs/msc2836/msc2836.go +++ b/internal/mscs/msc2836/msc2836.go @@ -16,10 +16,13 @@ package msc2836 import ( + "bytes" "context" "encoding/json" "fmt" + "io" "net/http" + "time" "github.com/matrix-org/dendrite/clientapi/jsonerror" fs "github.com/matrix-org/dendrite/federationsender/api" @@ -52,6 +55,16 @@ type EventRelationshipRequest struct { AutoJoin bool `json:"auto_join"` } +func NewEventRelationshipRequest(body io.Reader) (*EventRelationshipRequest, error) { + var relation EventRelationshipRequest + if err := json.NewDecoder(body).Decode(&relation); err != nil { + return nil, err + } + // Sanity check request and set defaults. + relation.applyDefaults() + return &relation, nil +} + func (r *EventRelationshipRequest) applyDefaults() { if r.Limit > 100 || r.Limit < 1 { r.Limit = 100 @@ -88,7 +101,10 @@ type EventRelationshipResponse struct { } // Enable this MSC -func Enable(base *setup.BaseDendrite, rsAPI roomserver.RoomserverInternalAPI, fsAPI fs.FederationSenderInternalAPI, userAPI userapi.UserInternalAPI) error { +func Enable( + base *setup.BaseDendrite, rsAPI roomserver.RoomserverInternalAPI, fsAPI fs.FederationSenderInternalAPI, + userAPI userapi.UserInternalAPI, keyRing gomatrixserverlib.JSONVerifier, +) error { db, err := NewDatabase(&base.Cfg.MSCs.Database) if err != nil { return fmt.Errorf("Cannot enable MSC2836: %w", err) @@ -154,88 +170,56 @@ func Enable(base *setup.BaseDendrite, rsAPI roomserver.RoomserverInternalAPI, fs }) base.PublicClientAPIMux.Handle("/unstable/event_relationships", - httputil.MakeAuthAPI("eventRelationships", userAPI, eventRelationshipHandler(db, rsAPI)), + httputil.MakeAuthAPI("eventRelationships", userAPI, eventRelationshipHandler(db, rsAPI, fsAPI)), ).Methods(http.MethodPost, http.MethodOptions) + + base.PublicFederationAPIMux.Handle("/unstable/event_relationships", httputil.MakeExternalAPI( + "msc2836_event_relationships", func(req *http.Request) util.JSONResponse { + fedReq, errResp := gomatrixserverlib.VerifyHTTPRequest( + req, time.Now(), base.Cfg.Global.ServerName, keyRing, + ) + if fedReq == nil { + return errResp + } + return federatedEventRelationship(req.Context(), fedReq, db, rsAPI) + }, + )).Methods(http.MethodPost, http.MethodOptions) return nil } type reqCtx struct { - ctx context.Context - rsAPI roomserver.RoomserverInternalAPI - req *EventRelationshipRequest - userID string + ctx context.Context + rsAPI roomserver.RoomserverInternalAPI + db Database + fsAPI fs.FederationSenderInternalAPI + req *EventRelationshipRequest + userID string + isFederatedRequest bool } -func eventRelationshipHandler(db Database, rsAPI roomserver.RoomserverInternalAPI) func(*http.Request, *userapi.Device) util.JSONResponse { +func eventRelationshipHandler(db Database, rsAPI roomserver.RoomserverInternalAPI, fsAPI fs.FederationSenderInternalAPI) func(*http.Request, *userapi.Device) util.JSONResponse { return func(req *http.Request, device *userapi.Device) util.JSONResponse { - var relation EventRelationshipRequest - if err := json.NewDecoder(req.Body).Decode(&relation); err != nil { + relation, err := NewEventRelationshipRequest(req.Body) + if err != nil { util.GetLogger(req.Context()).WithError(err).Error("failed to decode HTTP request as JSON") return util.JSONResponse{ Code: 400, JSON: jsonerror.BadJSON(fmt.Sprintf("invalid json: %s", err)), } } - // Sanity check request and set defaults. - relation.applyDefaults() - var res EventRelationshipResponse - var returnEvents []*gomatrixserverlib.HeaderedEvent rc := reqCtx{ - ctx: req.Context(), - req: &relation, - userID: device.UserID, - rsAPI: rsAPI, + ctx: req.Context(), + req: relation, + userID: device.UserID, + rsAPI: rsAPI, + isFederatedRequest: false, + db: db, } - - // Can the user see (according to history visibility) event_id? If no, reject the request, else continue. - // We should have the event being referenced so don't give any claimed room ID / servers - event := rc.getEventIfVisible(relation.EventID, "", nil) - if event == nil { - return util.JSONResponse{ - Code: 403, - JSON: jsonerror.Forbidden("Event does not exist or you are not authorised to see it"), - } + res, resErr := rc.process() + if resErr != nil { + return *resErr } - // Retrieve the event. Add it to response array. - returnEvents = append(returnEvents, event) - - if *relation.IncludeParent { - if parentEvent := rc.includeParent(event); parentEvent != nil { - returnEvents = append(returnEvents, parentEvent) - } - } - - if *relation.IncludeChildren { - remaining := relation.Limit - len(returnEvents) - if remaining > 0 { - children, resErr := rc.includeChildren(db, event.EventID(), remaining, *relation.RecentFirst) - if resErr != nil { - return *resErr - } - returnEvents = append(returnEvents, children...) - } - } - - remaining := relation.Limit - len(returnEvents) - var walkLimited bool - if remaining > 0 { - included := make(map[string]bool, len(returnEvents)) - for _, ev := range returnEvents { - included[ev.EventID()] = true - } - var events []*gomatrixserverlib.HeaderedEvent - events, walkLimited = walkThread( - req.Context(), db, &rc, included, remaining, - ) - returnEvents = append(returnEvents, events...) - } - res.Events = make([]gomatrixserverlib.ClientEvent, len(returnEvents)) - for i, ev := range returnEvents { - res.Events[i] = gomatrixserverlib.HeaderedToClientEvent(*ev, gomatrixserverlib.FormatAll) - } - res.Limited = remaining == 0 || walkLimited - return util.JSONResponse{ Code: 200, JSON: res, @@ -243,6 +227,88 @@ func eventRelationshipHandler(db Database, rsAPI roomserver.RoomserverInternalAP } } +func federatedEventRelationship(ctx context.Context, fedReq *gomatrixserverlib.FederationRequest, db Database, rsAPI roomserver.RoomserverInternalAPI) util.JSONResponse { + relation, err := NewEventRelationshipRequest(bytes.NewBuffer(fedReq.Content())) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("failed to decode HTTP request as JSON") + return util.JSONResponse{ + Code: 400, + JSON: jsonerror.BadJSON(fmt.Sprintf("invalid json: %s", err)), + } + } + rc := reqCtx{ + ctx: ctx, + req: relation, + userID: "", + rsAPI: rsAPI, + isFederatedRequest: true, + db: db, + } + res, resErr := rc.process() + if resErr != nil { + return *resErr + } + + return util.JSONResponse{ + Code: 200, + JSON: res, + } +} + +func (rc *reqCtx) process() (*EventRelationshipResponse, *util.JSONResponse) { + var res EventRelationshipResponse + var returnEvents []*gomatrixserverlib.HeaderedEvent + // Can the user see (according to history visibility) event_id? If no, reject the request, else continue. + // We should have the event being referenced so don't give any claimed room ID / servers + event := rc.getEventIfVisible(rc.req.EventID, "", nil) + if event == nil { + return nil, &util.JSONResponse{ + Code: 403, + JSON: jsonerror.Forbidden("Event does not exist or you are not authorised to see it"), + } + } + + // Retrieve the event. Add it to response array. + returnEvents = append(returnEvents, event) + + if *rc.req.IncludeParent { + if parentEvent := rc.includeParent(event); parentEvent != nil { + returnEvents = append(returnEvents, parentEvent) + } + } + + if *rc.req.IncludeChildren { + remaining := rc.req.Limit - len(returnEvents) + if remaining > 0 { + children, resErr := rc.includeChildren(rc.db, event.EventID(), remaining, *rc.req.RecentFirst) + if resErr != nil { + return nil, resErr + } + returnEvents = append(returnEvents, children...) + } + } + + remaining := rc.req.Limit - len(returnEvents) + var walkLimited bool + if remaining > 0 { + included := make(map[string]bool, len(returnEvents)) + for _, ev := range returnEvents { + included[ev.EventID()] = true + } + var events []*gomatrixserverlib.HeaderedEvent + events, walkLimited = walkThread( + rc.ctx, rc.db, rc, included, remaining, + ) + returnEvents = append(returnEvents, events...) + } + res.Events = make([]gomatrixserverlib.ClientEvent, len(returnEvents)) + for i, ev := range returnEvents { + res.Events[i] = gomatrixserverlib.HeaderedToClientEvent(*ev, gomatrixserverlib.FormatAll) + } + res.Limited = remaining == 0 || walkLimited + return &res, nil +} + // If include_parent: true and there is a valid m.relationship field in the event, // retrieve the referenced event. Apply history visibility check to that event and if it passes, add it to the response array. func (rc *reqCtx) includeParent(event *gomatrixserverlib.HeaderedEvent) (parent *gomatrixserverlib.HeaderedEvent) { diff --git a/internal/mscs/msc2836/msc2836_test.go b/internal/mscs/msc2836/msc2836_test.go index 795ead6a6..80ff98ffe 100644 --- a/internal/mscs/msc2836/msc2836_test.go +++ b/internal/mscs/msc2836/msc2836_test.go @@ -511,11 +511,12 @@ func injectEvents(t *testing.T, userAPI userapi.UserInternalAPI, rsAPI roomserve cfg.MSCs.Database.ConnectionString = "file:msc2836_test.db" cfg.MSCs.MSCs = []string{"msc2836"} base := &setup.BaseDendrite{ - Cfg: cfg, - PublicClientAPIMux: mux.NewRouter().PathPrefix(httputil.PublicClientPathPrefix).Subrouter(), + Cfg: cfg, + PublicClientAPIMux: mux.NewRouter().PathPrefix(httputil.PublicClientPathPrefix).Subrouter(), + PublicFederationAPIMux: mux.NewRouter().PathPrefix(httputil.PublicFederationPathPrefix).Subrouter(), } - err := msc2836.Enable(base, rsAPI, userAPI) + err := msc2836.Enable(base, rsAPI, nil, userAPI, nil) if err != nil { t.Fatalf("failed to enable MSC2836: %s", err) } diff --git a/internal/mscs/mscs.go b/internal/mscs/mscs.go index 41384a6a2..0a896ab0f 100644 --- a/internal/mscs/mscs.go +++ b/internal/mscs/mscs.go @@ -35,7 +35,7 @@ func Enable(base *setup.BaseDendrite, monolith *setup.Monolith) error { func EnableMSC(base *setup.BaseDendrite, monolith *setup.Monolith, msc string) error { switch msc { case "msc2836": - return msc2836.Enable(base, monolith.RoomserverAPI, monolith.FederationSenderAPI, monolith.UserAPI) + return msc2836.Enable(base, monolith.RoomserverAPI, monolith.FederationSenderAPI, monolith.UserAPI, monolith.KeyRing) default: return fmt.Errorf("EnableMSC: unknown msc '%s'", msc) }