From 561dc4c33b973859495413e1672e82150acf8fcf Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Mon, 2 Nov 2020 16:42:04 +0000 Subject: [PATCH] Implement visibility checks; stub out APIs for tests --- internal/mscs/msc2836/msc2836.go | 51 ++++- internal/mscs/msc2836/msc2836_test.go | 260 +++++++++++++++++++++++--- internal/mscs/mscs.go | 2 +- 3 files changed, 277 insertions(+), 36 deletions(-) diff --git a/internal/mscs/msc2836/msc2836.go b/internal/mscs/msc2836/msc2836.go index 64125301f..66e33ab8b 100644 --- a/internal/mscs/msc2836/msc2836.go +++ b/internal/mscs/msc2836/msc2836.go @@ -26,6 +26,7 @@ import ( "github.com/matrix-org/dendrite/internal/hooks" "github.com/matrix-org/dendrite/internal/httputil" "github.com/matrix-org/dendrite/internal/setup" + roomserver "github.com/matrix-org/dendrite/roomserver/api" userapi "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" @@ -80,7 +81,7 @@ type eventRelationshipResponse struct { } // Enable this MSC -func Enable(base *setup.BaseDendrite, userAPI userapi.UserInternalAPI) error { +func Enable(base *setup.BaseDendrite, rsAPI roomserver.RoomserverInternalAPI, userAPI userapi.UserInternalAPI) error { db, err := NewDatabase(&base.Cfg.MSCs.Database) if err != nil { return fmt.Errorf("Cannot enable MSC2836: %w", err) @@ -112,7 +113,7 @@ func Enable(base *setup.BaseDendrite, userAPI userapi.UserInternalAPI) error { var returnEvents []*gomatrixserverlib.HeaderedEvent // Can the user see (according to history visibility) event_id? If no, reject the request, else continue. - event := getEventIfVisible(req.Context(), relation.EventID, device.UserID) + event := getEventIfVisible(req.Context(), rsAPI, relation.EventID, device.UserID) if event == nil { return util.JSONResponse{ Code: 403, @@ -124,7 +125,7 @@ func Enable(base *setup.BaseDendrite, userAPI userapi.UserInternalAPI) error { returnEvents = append(returnEvents, event) if *relation.IncludeParent { - if parentEvent := includeParent(req.Context(), event, device.UserID); parentEvent != nil { + if parentEvent := includeParent(req.Context(), rsAPI, event, device.UserID); parentEvent != nil { returnEvents = append(returnEvents, parentEvent) } } @@ -132,7 +133,7 @@ func Enable(base *setup.BaseDendrite, userAPI userapi.UserInternalAPI) error { if *relation.IncludeChildren { remaining := relation.Limit - len(returnEvents) if remaining > 0 { - children, resErr := includeChildren(req.Context(), db, event.EventID(), remaining, *relation.RecentFirst, device.UserID) + children, resErr := includeChildren(req.Context(), rsAPI, db, event.EventID(), remaining, *relation.RecentFirst, device.UserID) if resErr != nil { return *resErr } @@ -166,18 +167,18 @@ func Enable(base *setup.BaseDendrite, userAPI userapi.UserInternalAPI) error { // 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 includeParent(ctx context.Context, event *gomatrixserverlib.HeaderedEvent, userID string) (parent *gomatrixserverlib.HeaderedEvent) { +func includeParent(ctx context.Context, rsAPI roomserver.RoomserverInternalAPI, event *gomatrixserverlib.HeaderedEvent, userID string) (parent *gomatrixserverlib.HeaderedEvent) { parentID, _ := parentChildEventIDs(event) if parentID == "" { return nil } - return getEventIfVisible(ctx, parentID, userID) + return getEventIfVisible(ctx, rsAPI, parentID, userID) } // If include_children: true, lookup all events which have event_id as an m.relationship // Apply history visibility checks to all these events and add the ones which pass into the response array, // honouring the recent_first flag and the limit. -func includeChildren(ctx context.Context, db Database, parentID string, limit int, recentFirst bool, userID string) ([]*gomatrixserverlib.HeaderedEvent, *util.JSONResponse) { +func includeChildren(ctx context.Context, rsAPI roomserver.RoomserverInternalAPI, db Database, parentID string, limit int, recentFirst bool, userID string) ([]*gomatrixserverlib.HeaderedEvent, *util.JSONResponse) { children, err := db.ChildrenForParent(ctx, parentID) if err != nil { util.GetLogger(ctx).WithError(err).Error("failed to get ChildrenForParent") @@ -186,7 +187,7 @@ func includeChildren(ctx context.Context, db Database, parentID string, limit in } var childEvents []*gomatrixserverlib.HeaderedEvent for _, child := range children { - childEvent := getEventIfVisible(ctx, child, userID) + childEvent := getEventIfVisible(ctx, rsAPI, child, userID) if childEvent != nil { childEvents = append(childEvents, childEvent) } @@ -208,6 +209,36 @@ func walkThread(ctx context.Context, db Database, limit int) ([]*gomatrixserverl return nil, false } -func getEventIfVisible(ctx context.Context, eventID, userID string) *gomatrixserverlib.HeaderedEvent { - return nil +func getEventIfVisible(ctx context.Context, rsAPI roomserver.RoomserverInternalAPI, eventID, userID string) *gomatrixserverlib.HeaderedEvent { + var queryEventsRes roomserver.QueryEventsByIDResponse + err := rsAPI.QueryEventsByID(ctx, &roomserver.QueryEventsByIDRequest{ + EventIDs: []string{eventID}, + }, &queryEventsRes) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("getEventIfVisible: failed to QueryEventsByID") + return nil + } + if len(queryEventsRes.Events) == 0 { + util.GetLogger(ctx).Infof("event does not exist") + return nil // event does not exist + } + event := queryEventsRes.Events[0] + + // Allow events if the member is in the room + // TODO: This does not honour history_visibility + // TODO: This does not honour m.room.create content + var queryMembershipRes roomserver.QueryMembershipForUserResponse + err = rsAPI.QueryMembershipForUser(ctx, &roomserver.QueryMembershipForUserRequest{ + RoomID: event.RoomID(), + UserID: userID, + }, &queryMembershipRes) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("getEventIfVisible: failed to QueryMembershipForUser") + return nil + } + if !queryMembershipRes.IsInRoom { + util.GetLogger(ctx).Infof("user not in room") + return nil + } + return &event } diff --git a/internal/mscs/msc2836/msc2836_test.go b/internal/mscs/msc2836/msc2836_test.go index 146673cd4..6efeab668 100644 --- a/internal/mscs/msc2836/msc2836_test.go +++ b/internal/mscs/msc2836/msc2836_test.go @@ -3,6 +3,7 @@ package msc2836_test import ( "bytes" "context" + "crypto/ed25519" "encoding/json" "fmt" "net/http" @@ -17,6 +18,7 @@ import ( "github.com/matrix-org/dendrite/internal/httputil" "github.com/matrix-org/dendrite/internal/mscs/msc2836" "github.com/matrix-org/dendrite/internal/setup" + roomserver "github.com/matrix-org/dendrite/roomserver/api" userapi "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/gomatrixserverlib" ) @@ -25,6 +27,8 @@ var ( client = &http.Client{ Timeout: 10 * time.Second, } + constTrue = true + constFalse = false ) // Basic sanity check of MSC2836 logic. Injects a thread that looks like: @@ -39,13 +43,176 @@ var ( // H // And makes sure POST /relationships works with various parameters func TestMSC2836(t *testing.T) { - router := injectEvents(t) + alice := "@alice:localhost" + bob := "@bob:localhost" + charlie := "@charlie:localhost" + roomIDA := "!alice:localhost" + roomIDB := "!bob:localhost" + roomIDC := "!charlie:localhost" + // give access tokens to all three users + nopUserAPI := &testUserAPI{ + accessTokens: make(map[string]userapi.Device), + } + nopUserAPI.accessTokens["alice"] = userapi.Device{ + AccessToken: "alice", + DisplayName: "Alice", + UserID: alice, + } + nopUserAPI.accessTokens["bob"] = userapi.Device{ + AccessToken: "bob", + DisplayName: "Bob", + UserID: bob, + } + nopUserAPI.accessTokens["charlie"] = userapi.Device{ + AccessToken: "charlie", + DisplayName: "Charles", + UserID: charlie, + } + eventA := mustCreateEvent(t, gomatrixserverlib.RoomVersionV6, fledglingEvent{ + RoomID: roomIDA, + Sender: alice, + Type: "m.room.message", + Content: map[string]interface{}{ + "body": "[A] Do you know shelties?", + }, + }) + eventB := mustCreateEvent(t, gomatrixserverlib.RoomVersionV6, fledglingEvent{ + RoomID: roomIDB, + Sender: bob, + Type: "m.room.message", + Content: map[string]interface{}{ + "body": "[B] I <3 shelties", + "m.relationship": map[string]string{ + "rel_type": "m.reference", + "event_id": eventA.EventID(), + }, + }, + }) + eventC := mustCreateEvent(t, gomatrixserverlib.RoomVersionV6, fledglingEvent{ + RoomID: roomIDB, + Sender: bob, + Type: "m.room.message", + Content: map[string]interface{}{ + "body": "[C] like so much", + "m.relationship": map[string]string{ + "rel_type": "m.reference", + "event_id": eventB.EventID(), + }, + }, + }) + eventD := mustCreateEvent(t, gomatrixserverlib.RoomVersionV6, fledglingEvent{ + RoomID: roomIDA, + Sender: alice, + Type: "m.room.message", + Content: map[string]interface{}{ + "body": "[D] but what are shelties???", + "m.relationship": map[string]string{ + "rel_type": "m.reference", + "event_id": eventB.EventID(), + }, + }, + }) + eventE := mustCreateEvent(t, gomatrixserverlib.RoomVersionV6, fledglingEvent{ + RoomID: roomIDB, + Sender: bob, + Type: "m.room.message", + Content: map[string]interface{}{ + "body": "[E] seriously???", + "m.relationship": map[string]string{ + "rel_type": "m.reference", + "event_id": eventD.EventID(), + }, + }, + }) + eventF := mustCreateEvent(t, gomatrixserverlib.RoomVersionV6, fledglingEvent{ + RoomID: roomIDC, + Sender: charlie, + Type: "m.room.message", + Content: map[string]interface{}{ + "body": "[F] omg how do you not know what shelties are", + "m.relationship": map[string]string{ + "rel_type": "m.reference", + "event_id": eventD.EventID(), + }, + }, + }) + eventG := mustCreateEvent(t, gomatrixserverlib.RoomVersionV6, fledglingEvent{ + RoomID: roomIDA, + Sender: alice, + Type: "m.room.message", + Content: map[string]interface{}{ + "body": "[G] looked it up, it's a sheltered person?", + "m.relationship": map[string]string{ + "rel_type": "m.reference", + "event_id": eventD.EventID(), + }, + }, + }) + eventH := mustCreateEvent(t, gomatrixserverlib.RoomVersionV6, fledglingEvent{ + RoomID: roomIDB, + Sender: bob, + Type: "m.room.message", + Content: map[string]interface{}{ + "body": "[H] it's a dog!!!!!", + "m.relationship": map[string]string{ + "rel_type": "m.reference", + "event_id": eventE.EventID(), + }, + }, + }) + // make everyone joined to each other's rooms + nopRsAPI := &testRoomserverAPI{ + userToJoinedRooms: map[string][]string{ + alice: []string{roomIDA, roomIDB, roomIDC}, + bob: []string{roomIDA, roomIDB, roomIDC}, + charlie: []string{roomIDA, roomIDB, roomIDC}, + }, + events: map[string]*gomatrixserverlib.HeaderedEvent{ + eventA.EventID(): eventA, + eventB.EventID(): eventB, + eventC.EventID(): eventC, + eventD.EventID(): eventD, + eventE.EventID(): eventE, + eventF.EventID(): eventF, + eventG.EventID(): eventG, + eventH.EventID(): eventH, + }, + } + router := injectEvents(t, nopUserAPI, nopRsAPI, []*gomatrixserverlib.HeaderedEvent{ + eventA, eventB, eventC, eventD, eventE, eventF, eventG, eventH, + }) + cancel := runServer(t, router) + defer cancel() + + t.Run("returns 403 on invalid event IDs", func(t *testing.T) { + res := postRelationships(t, "alice", &msc2836.EventRelationshipRequest{ + EventID: "$invalid", + }) + if res.StatusCode != 403 { + out, _ := nethttputil.DumpResponse(res, true) + t.Fatalf("failed to perform request: %s", string(out)) + } + }) + t.Run("returns the parent if include_parent is true", func(t *testing.T) { + res := postRelationships(t, "alice", &msc2836.EventRelationshipRequest{ + EventID: eventB.EventID(), + IncludeParent: &constTrue, + Limit: 1, + }) + if res.StatusCode != 200 { + out, _ := nethttputil.DumpResponse(res, true) + t.Fatalf("failed to perform request: %s", string(out)) + } + }) +} + +func runServer(t *testing.T, router *mux.Router) func() { + t.Helper() externalServ := &http.Server{ Addr: string(":8009"), WriteTimeout: 60 * time.Second, Handler: router, } - defer externalServ.Shutdown(context.TODO()) go func() { err := externalServ.ListenAndServe() if err != nil { @@ -54,13 +221,8 @@ func TestMSC2836(t *testing.T) { }() // wait to listen on the port time.Sleep(500 * time.Millisecond) - - res := postRelationships(t, "alice", &msc2836.EventRelationshipRequest{ - EventID: "$invalid", - }) - if res.StatusCode != 403 { - out, _ := nethttputil.DumpResponse(res, true) - t.Fatalf("failed to perform request: %s", string(out)) + return func() { + externalServ.Shutdown(context.TODO()) } } @@ -135,7 +297,38 @@ func (u *testUserAPI) QuerySearchProfiles(ctx context.Context, req *userapi.Quer return nil } -func injectEvents(t *testing.T) *mux.Router { +type testRoomserverAPI struct { + // use a trace API as it implements method stubs so we don't need to have them here. + // We'll override the functions we care about. + roomserver.RoomserverInternalAPITrace + userToJoinedRooms map[string][]string + events map[string]*gomatrixserverlib.HeaderedEvent +} + +func (r *testRoomserverAPI) QueryEventsByID(ctx context.Context, req *roomserver.QueryEventsByIDRequest, res *roomserver.QueryEventsByIDResponse) error { + for _, eventID := range req.EventIDs { + ev := r.events[eventID] + if ev != nil { + res.Events = append(res.Events, *ev) + } + } + return nil +} + +func (r *testRoomserverAPI) QueryMembershipForUser(ctx context.Context, req *roomserver.QueryMembershipForUserRequest, res *roomserver.QueryMembershipForUserResponse) error { + rooms := r.userToJoinedRooms[req.UserID] + for _, roomID := range rooms { + if roomID == req.RoomID { + res.IsInRoom = true + res.HasBeenInRoom = true + res.Membership = "join" + break + } + } + return nil +} + +func injectEvents(t *testing.T, userAPI userapi.UserInternalAPI, rsAPI roomserver.RoomserverInternalAPI, events []*gomatrixserverlib.HeaderedEvent) *mux.Router { t.Helper() cfg := &config.Dendrite{} cfg.Defaults() @@ -146,27 +339,44 @@ func injectEvents(t *testing.T) *mux.Router { Cfg: cfg, PublicClientAPIMux: mux.NewRouter().PathPrefix(httputil.PublicClientPathPrefix).Subrouter(), } - nopUserAPI := &testUserAPI{ - accessTokens: make(map[string]userapi.Device), - } - nopUserAPI.accessTokens["alice"] = userapi.Device{ - AccessToken: "alice", - DisplayName: "Alice", - UserID: "@alice:localhost", - } - nopUserAPI.accessTokens["bob"] = userapi.Device{ - AccessToken: "bob", - DisplayName: "Bob", - UserID: "@bob:localhost", - } - err := msc2836.Enable(base, nopUserAPI) + err := msc2836.Enable(base, rsAPI, userAPI) if err != nil { t.Fatalf("failed to enable MSC2836: %s", err) } - var events []*gomatrixserverlib.HeaderedEvent for _, ev := range events { hooks.Run(hooks.KindNewEvent, ev) } return base.PublicClientAPIMux } + +type fledglingEvent struct { + Type string + StateKey *string + Content interface{} + Sender string + RoomID string +} + +func mustCreateEvent(t *testing.T, roomVer gomatrixserverlib.RoomVersion, ev fledglingEvent) (result *gomatrixserverlib.HeaderedEvent) { + t.Helper() + seed := make([]byte, ed25519.SeedSize) // zero seed + key := ed25519.NewKeyFromSeed(seed) + eb := gomatrixserverlib.EventBuilder{ + Sender: ev.Sender, + Depth: 999, + Type: ev.Type, + StateKey: ev.StateKey, + RoomID: ev.RoomID, + } + err := eb.SetContent(ev.Content) + if err != nil { + t.Fatalf("mustCreateEvent: failed to marshal event content %+v", ev.Content) + } + signedEvent, err := eb.Build(time.Now(), gomatrixserverlib.ServerName("localhost"), "ed25519:test", key, roomVer) + if err != nil { + t.Fatalf("mustCreateEvent: failed to sign event: %s", err) + } + h := signedEvent.Headered(roomVer) + return &h +} diff --git a/internal/mscs/mscs.go b/internal/mscs/mscs.go index 7a1c2af4d..a1c358cfe 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.UserAPI) + return msc2836.Enable(base, monolith.RoomserverAPI, monolith.UserAPI) default: return fmt.Errorf("EnableMSC: unknown msc '%s'", msc) }