Refactor msc2836 to allow handling from federation

This commit is contained in:
Kegan Dougal 2020-11-18 17:32:50 +00:00
parent 37790d6c68
commit 46cbbc3fc9
3 changed files with 136 additions and 69 deletions

View file

@ -16,10 +16,13 @@
package msc2836 package msc2836
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"net/http" "net/http"
"time"
"github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/clientapi/jsonerror"
fs "github.com/matrix-org/dendrite/federationsender/api" fs "github.com/matrix-org/dendrite/federationsender/api"
@ -52,6 +55,16 @@ type EventRelationshipRequest struct {
AutoJoin bool `json:"auto_join"` 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() { func (r *EventRelationshipRequest) applyDefaults() {
if r.Limit > 100 || r.Limit < 1 { if r.Limit > 100 || r.Limit < 1 {
r.Limit = 100 r.Limit = 100
@ -88,7 +101,10 @@ type EventRelationshipResponse struct {
} }
// Enable this MSC // 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) db, err := NewDatabase(&base.Cfg.MSCs.Database)
if err != nil { if err != nil {
return fmt.Errorf("Cannot enable MSC2836: %w", err) 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", 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) ).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 return nil
} }
type reqCtx struct { type reqCtx struct {
ctx context.Context ctx context.Context
rsAPI roomserver.RoomserverInternalAPI rsAPI roomserver.RoomserverInternalAPI
req *EventRelationshipRequest db Database
userID string 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 { return func(req *http.Request, device *userapi.Device) util.JSONResponse {
var relation EventRelationshipRequest relation, err := NewEventRelationshipRequest(req.Body)
if err := json.NewDecoder(req.Body).Decode(&relation); err != nil { if err != nil {
util.GetLogger(req.Context()).WithError(err).Error("failed to decode HTTP request as JSON") util.GetLogger(req.Context()).WithError(err).Error("failed to decode HTTP request as JSON")
return util.JSONResponse{ return util.JSONResponse{
Code: 400, Code: 400,
JSON: jsonerror.BadJSON(fmt.Sprintf("invalid json: %s", err)), 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{ rc := reqCtx{
ctx: req.Context(), ctx: req.Context(),
req: &relation, req: relation,
userID: device.UserID, userID: device.UserID,
rsAPI: rsAPI, rsAPI: rsAPI,
isFederatedRequest: false,
db: db,
} }
res, resErr := rc.process()
// Can the user see (according to history visibility) event_id? If no, reject the request, else continue. if resErr != nil {
// We should have the event being referenced so don't give any claimed room ID / servers return *resErr
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"),
}
} }
// 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{ return util.JSONResponse{
Code: 200, Code: 200,
JSON: res, 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, // 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. // 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) { func (rc *reqCtx) includeParent(event *gomatrixserverlib.HeaderedEvent) (parent *gomatrixserverlib.HeaderedEvent) {

View file

@ -511,11 +511,12 @@ func injectEvents(t *testing.T, userAPI userapi.UserInternalAPI, rsAPI roomserve
cfg.MSCs.Database.ConnectionString = "file:msc2836_test.db" cfg.MSCs.Database.ConnectionString = "file:msc2836_test.db"
cfg.MSCs.MSCs = []string{"msc2836"} cfg.MSCs.MSCs = []string{"msc2836"}
base := &setup.BaseDendrite{ base := &setup.BaseDendrite{
Cfg: cfg, Cfg: cfg,
PublicClientAPIMux: mux.NewRouter().PathPrefix(httputil.PublicClientPathPrefix).Subrouter(), 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 { if err != nil {
t.Fatalf("failed to enable MSC2836: %s", err) t.Fatalf("failed to enable MSC2836: %s", err)
} }

View file

@ -35,7 +35,7 @@ func Enable(base *setup.BaseDendrite, monolith *setup.Monolith) error {
func EnableMSC(base *setup.BaseDendrite, monolith *setup.Monolith, msc string) error { func EnableMSC(base *setup.BaseDendrite, monolith *setup.Monolith, msc string) error {
switch msc { switch msc {
case "msc2836": 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: default:
return fmt.Errorf("EnableMSC: unknown msc '%s'", msc) return fmt.Errorf("EnableMSC: unknown msc '%s'", msc)
} }