From cdd93110fa61fa9b8985fca66b9d3ec189657947 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 23 Jun 2021 16:19:39 +0100 Subject: [PATCH 1/3] Test validation on send_{join,knock,leave} --- go.mod | 1 + internal/federation/server.go | 17 ++++ tests/federation_room_join_test.go | 123 +++++++++++++++++++++++++++++ tests/msc2403_test.go | 8 ++ 4 files changed, 149 insertions(+) diff --git a/go.mod b/go.mod index b50041cc..123bfd01 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/docker/go-connections v0.4.0 github.com/docker/go-units v0.4.0 // indirect github.com/gorilla/mux v1.8.0 + github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26 github.com/matrix-org/gomatrixserverlib v0.0.0-20210624115417-42ac4e797a58 github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7 github.com/opencontainers/go-digest v1.0.0 // indirect diff --git a/internal/federation/server.go b/internal/federation/server.go index 2bf7555b..510068e9 100644 --- a/internal/federation/server.go +++ b/internal/federation/server.go @@ -155,6 +155,23 @@ func (s *Server) FederationClient(deployment *docker.Deployment) *gomatrixserver return f } +// SendFederationRequest signs and sends an arbitrary federation request from this server. +// +// The requests will be routed according to the deployment map in `deployment`. +func (s *Server) SendFederationRequest(deployment *docker.Deployment, req gomatrixserverlib.FederationRequest, resBody interface{}) error { + if err := req.Sign(gomatrixserverlib.ServerName(s.ServerName), s.KeyID, s.Priv); err != nil { + return err + } + + httpReq, err := req.HTTPRequest() + if err != nil { + return err + } + + httpClient := gomatrixserverlib.NewClient(gomatrixserverlib.WithTransport(&docker.RoundTripper{Deployment: deployment})) + return httpClient.DoRequestAndParseResponse(context.Background(), httpReq, resBody) +} + // MustCreateEvent will create and sign a new latest event for the given room. // It does not insert this event into the room however. See ServerRoom.AddEvent for that. func (s *Server) MustCreateEvent(t *testing.T, room *ServerRoom, ev b.Event) *gomatrixserverlib.Event { diff --git a/tests/federation_room_join_test.go b/tests/federation_room_join_test.go index 3fcb5184..f0ec0f24 100644 --- a/tests/federation_room_join_test.go +++ b/tests/federation_room_join_test.go @@ -2,10 +2,12 @@ package tests import ( "encoding/json" + "fmt" "net/http" "net/url" "testing" + "github.com/matrix-org/gomatrix" "github.com/matrix-org/gomatrixserverlib" "github.com/tidwall/sjson" @@ -192,3 +194,124 @@ func TestJoinFederatedRoomWithUnverifiableEvents(t *testing.T) { alice.JoinRoom(t, roomAlias, nil) }) } + +// This test checks that we cannot submit anything via /v1/send_join except a join. +func TestCannotSendNonJoinViaSendJoinV1(t *testing.T) { + testValidationForSendMembershipEndpoint(t, "/_matrix/federation/v1/send_join", "join", nil) +} + +// This test checks that we cannot submit anything via /v2/send_join except a join. +func TestCannotSendNonJoinViaSendJoinV2(t *testing.T) { + testValidationForSendMembershipEndpoint(t, "/_matrix/federation/v2/send_join", "join", nil) +} + +// This test checks that we cannot submit anything via /v1/send_leave except a leave. +func TestCannotSendNonLeaveViaSendLeaveV1(t *testing.T) { + testValidationForSendMembershipEndpoint(t, "/_matrix/federation/v1/send_leave", "leave", nil) +} + +// This test checks that we cannot submit anything via /v2/send_leave except a leave. +func TestCannotSendNonLeaveViaSendLeaveV2(t *testing.T) { + testValidationForSendMembershipEndpoint(t, "/_matrix/federation/v2/send_leave", "leave", nil) +} + +// testValidationForSendMembershipEndpoint attempts to submit a range of events via the given endpoint +// and checks that they are all rejected. +func testValidationForSendMembershipEndpoint(t *testing.T, baseApiPath, expectedMembership string, createRoomOpts map[string]interface{}) { + if createRoomOpts == nil { + createRoomOpts = map[string]interface{}{} + } + + deployment := Deploy(t, b.BlueprintAlice) + defer deployment.Destroy(t) + + srv := federation.NewServer(t, deployment, + federation.HandleKeyRequests(), + federation.HandleTransactionRequests(nil, nil), + ) + cancel := srv.Listen() + defer cancel() + + // alice creates a room, and charlie joins it + alice := deployment.Client(t, "hs1", "@alice:hs1") + roomId := alice.CreateRoom(t, createRoomOpts) + charlie := srv.UserID("charlie") + room := srv.MustJoinRoom(t, deployment, "hs1", roomId, charlie) + + // a helper function which makes a send_* request to the given path and checks + // that it fails with a 400 error + assertRequestFails := func(t *testing.T, event *gomatrixserverlib.Event) { + req := gomatrixserverlib.NewFederationRequest("PUT", "hs1", + fmt.Sprintf("%s/%s/%s", + baseApiPath, + url.PathEscape(event.RoomID()), + url.PathEscape(event.EventID()), + )) + if err := req.SetContent(event); err != nil { + t.Errorf("req.SetContent: %v", err) + return + } + + err := srv.SendFederationRequest(deployment, req, map[string]interface{}{}) + if err == nil { + t.Errorf("send request returned 200") + return + } + + httpError, ok := err.(gomatrix.HTTPError) + if !ok { + t.Errorf("not an HTTPError: %v", err) + return + } + + t.Logf("%s returned %d/%s", baseApiPath, httpError.Code, string(httpError.Contents)) + if httpError.Code != 400 { + t.Errorf("expected 400, got %d", httpError.Code) + } + } + + t.Run("regular event", func(t *testing.T) { + event := srv.MustCreateEvent(t, room, b.Event{ + Type: "m.room.message", + Sender: charlie, + Content: map[string]interface{}{"body": "bzz"}, + }) + assertRequestFails(t, event) + }) + t.Run("non-state membership event", func(t *testing.T) { + event := srv.MustCreateEvent(t, room, b.Event{ + Type: "m.room.message", + Sender: charlie, + Content: map[string]interface{}{"body": "bzz"}, + }) + assertRequestFails(t, event) + }) + + // try membership events of various types, other than that expected by + // the endpoint + for _, membershipType := range []string{"join", "leave", "knock", "invite"} { + if membershipType == expectedMembership { + continue + } + event := srv.MustCreateEvent(t, room, b.Event{ + Type: "m.room.member", + Sender: charlie, + StateKey: &charlie, + Content: map[string]interface{}{"membership": membershipType}, + }) + t.Run(membershipType+" event", func(t *testing.T) { + assertRequestFails(t, event) + }) + } + + // right sort of membership, but mismatched state_key + t.Run("event with mismatched state key", func(t *testing.T) { + event := srv.MustCreateEvent(t, room, b.Event{ + Type: "m.room.member", + Sender: charlie, + StateKey: b.Ptr(srv.UserID("doris")), + Content: map[string]interface{}{"membership": expectedMembership}, + }) + assertRequestFails(t, event) + }) +} diff --git a/tests/msc2403_test.go b/tests/msc2403_test.go index 23751cdf..4fe9aa70 100644 --- a/tests/msc2403_test.go +++ b/tests/msc2403_test.go @@ -465,5 +465,13 @@ func publishAndCheckRoomJoinRule(t *testing.T, c *client.CSAPI, roomID, expected if !roomFound { t.Fatalf("Room was not present in public room directory response") } +} +// TestCannotSendNonKnockViaSendKnock checks that we cannot submit anything via /send_knock except a knock +func TestCannotSendNonKnockViaSendKnock(t *testing.T) { + testValidationForSendMembershipEndpoint(t, "knock", "/_matrix/federation/v2/send_leave", + map[string]interface{}{ + "room_version": "7", + }, + ) } From 198df4f0ffee1ad2d0fb9dd9ee84aa39818eeb8c Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Mon, 12 Jul 2021 15:51:27 +0100 Subject: [PATCH 2/3] Fix up test and address review comments --- tests/federation_room_join_test.go | 18 ++++++++++-------- tests/msc2403_test.go | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/federation_room_join_test.go b/tests/federation_room_join_test.go index f0ec0f24..128f61fb 100644 --- a/tests/federation_room_join_test.go +++ b/tests/federation_room_join_test.go @@ -241,18 +241,20 @@ func testValidationForSendMembershipEndpoint(t *testing.T, baseApiPath, expected // a helper function which makes a send_* request to the given path and checks // that it fails with a 400 error assertRequestFails := func(t *testing.T, event *gomatrixserverlib.Event) { - req := gomatrixserverlib.NewFederationRequest("PUT", "hs1", - fmt.Sprintf("%s/%s/%s", - baseApiPath, - url.PathEscape(event.RoomID()), - url.PathEscape(event.EventID()), - )) + path := fmt.Sprintf("%s/%s/%s", + baseApiPath, + url.PathEscape(event.RoomID()), + url.PathEscape(event.EventID()), + ) + t.Logf("PUT %s", path) + req := gomatrixserverlib.NewFederationRequest("PUT", "hs1", path) if err := req.SetContent(event); err != nil { t.Errorf("req.SetContent: %v", err) return } - err := srv.SendFederationRequest(deployment, req, map[string]interface{}{}) + var res map[string]interface{} + err := srv.SendFederationRequest(deployment, req, &res) if err == nil { t.Errorf("send request returned 200") return @@ -280,7 +282,7 @@ func testValidationForSendMembershipEndpoint(t *testing.T, baseApiPath, expected }) t.Run("non-state membership event", func(t *testing.T) { event := srv.MustCreateEvent(t, room, b.Event{ - Type: "m.room.message", + Type: "m.room.member", Sender: charlie, Content: map[string]interface{}{"body": "bzz"}, }) diff --git a/tests/msc2403_test.go b/tests/msc2403_test.go index 4fe9aa70..4e0cde4f 100644 --- a/tests/msc2403_test.go +++ b/tests/msc2403_test.go @@ -469,7 +469,7 @@ func publishAndCheckRoomJoinRule(t *testing.T, c *client.CSAPI, roomID, expected // TestCannotSendNonKnockViaSendKnock checks that we cannot submit anything via /send_knock except a knock func TestCannotSendNonKnockViaSendKnock(t *testing.T) { - testValidationForSendMembershipEndpoint(t, "knock", "/_matrix/federation/v2/send_leave", + testValidationForSendMembershipEndpoint(t, "/_matrix/federation/v1/send_knock", "knock", map[string]interface{}{ "room_version": "7", }, From 187fb5b9661d50593fcfd7483bc884895a72f6b3 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Mon, 12 Jul 2021 15:53:28 +0100 Subject: [PATCH 3/3] another review comment --- tests/federation_room_join_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/federation_room_join_test.go b/tests/federation_room_join_test.go index 128f61fb..dbad1212 100644 --- a/tests/federation_room_join_test.go +++ b/tests/federation_room_join_test.go @@ -219,7 +219,7 @@ func TestCannotSendNonLeaveViaSendLeaveV2(t *testing.T) { // and checks that they are all rejected. func testValidationForSendMembershipEndpoint(t *testing.T, baseApiPath, expectedMembership string, createRoomOpts map[string]interface{}) { if createRoomOpts == nil { - createRoomOpts = map[string]interface{}{} + createRoomOpts = make(map[string]interface{}) } deployment := Deploy(t, b.BlueprintAlice)