Implement visibility checks; stub out APIs for tests

This commit is contained in:
Kegan Dougal 2020-11-02 16:42:04 +00:00
parent ba6ed97282
commit 561dc4c33b
3 changed files with 277 additions and 36 deletions

View file

@ -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
}

View file

@ -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
}

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 {
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)
}