From 7c36fb78a729dcce174a5d1e577edeeeb9ca806d Mon Sep 17 00:00:00 2001 From: Kegsay Date: Mon, 15 Jun 2020 16:57:59 +0100 Subject: [PATCH] Fix rooms v3 url paths for good - with tests (#1130) * Fix rooms v3 url paths for good - with tests - Add a test rig around `federationapi` to test routing. - Use `JSONVerifier` over `KeyRing` so we can stub things out more easily. - Add `test.NopJSONVerifier` which verifies nothing. - Add `base.BaseMux` which is the original `mux.Router` used to spawn public/internal routers. - Listen on `base.BaseMux` and not the default serve mux as it cleans paths which we don't want. - Factor out `ListenAndServe` to `test.ListenAndServe` and add flag for listening on TLS. * Fix comments * Linting --- cmd/create-room-events/main.go | 5 +- cmd/dendrite-demo-libp2p/main.go | 6 +- cmd/dendrite-demo-yggdrasil/main.go | 29 ++++---- cmd/dendrite-monolith-server/main.go | 4 +- cmd/dendritejs/jsServer.go | 2 +- cmd/dendritejs/main.go | 7 +- federationapi/federationapi.go | 4 +- federationapi/federationapi_test.go | 102 +++++++++++++++++++++++++++ federationapi/routing/invite.go | 2 +- federationapi/routing/join.go | 2 +- federationapi/routing/leave.go | 2 +- federationapi/routing/routing.go | 2 +- federationapi/routing/send.go | 2 +- federationapi/routing/send_test.go | 12 +--- internal/httputil/httpapi.go | 7 +- internal/setup/base.go | 5 +- internal/test/keyring.go | 31 ++++++++ internal/test/server.go | 50 ++++++++++++- userapi/userapi_test.go | 30 +------- 19 files changed, 228 insertions(+), 76 deletions(-) create mode 100644 federationapi/federationapi_test.go create mode 100644 internal/test/keyring.go diff --git a/cmd/create-room-events/main.go b/cmd/create-room-events/main.go index ebce953c..afe97464 100644 --- a/cmd/create-room-events/main.go +++ b/cmd/create-room-events/main.go @@ -47,6 +47,7 @@ var ( userID = flag.String("user-id", "@userid:$SERVER_NAME", "The user ID to use as the event sender") messageCount = flag.Int("message-count", 10, "The number of m.room.messsage events to generate") format = flag.String("Format", "InputRoomEvent", "The output format to use for the messages: InputRoomEvent or Event") + ver = flag.String("version", string(gomatrixserverlib.RoomVersionV1), "Room version to generate events as") ) // By default we use a private key of 0. @@ -109,7 +110,7 @@ func buildAndOutput() gomatrixserverlib.EventReference { event, err := b.Build( now, name, key, privateKey, - gomatrixserverlib.RoomVersionV1, + gomatrixserverlib.RoomVersion(*ver), ) if err != nil { panic(err) @@ -127,7 +128,7 @@ func writeEvent(event gomatrixserverlib.Event) { if *format == "InputRoomEvent" { var ire api.InputRoomEvent ire.Kind = api.KindNew - ire.Event = event.Headered(gomatrixserverlib.RoomVersionV1) + ire.Event = event.Headered(gomatrixserverlib.RoomVersion(*ver)) authEventIDs := []string{} for _, ref := range b.AuthEvents.([]gomatrixserverlib.EventReference) { authEventIDs = append(authEventIDs, ref.EventID) diff --git a/cmd/dendrite-demo-libp2p/main.go b/cmd/dendrite-demo-libp2p/main.go index f215606e..0e757de9 100644 --- a/cmd/dendrite-demo-libp2p/main.go +++ b/cmd/dendrite-demo-libp2p/main.go @@ -173,7 +173,7 @@ func main() { monolith.AddAllPublicRoutes(base.Base.PublicAPIMux) httputil.SetupHTTPAPI( - http.DefaultServeMux, + base.Base.BaseMux, base.Base.PublicAPIMux, base.Base.InternalAPIMux, &cfg, @@ -184,7 +184,7 @@ func main() { go func() { httpBindAddr := fmt.Sprintf(":%d", *instancePort) logrus.Info("Listening on ", httpBindAddr) - logrus.Fatal(http.ListenAndServe(httpBindAddr, nil)) + logrus.Fatal(http.ListenAndServe(httpBindAddr, base.Base.BaseMux)) }() // Expose the matrix APIs also via libp2p if base.LibP2P != nil { @@ -197,7 +197,7 @@ func main() { defer func() { logrus.Fatal(listener.Close()) }() - logrus.Fatal(http.Serve(listener, nil)) + logrus.Fatal(http.Serve(listener, base.Base.BaseMux)) }() } diff --git a/cmd/dendrite-demo-yggdrasil/main.go b/cmd/dendrite-demo-yggdrasil/main.go index e4d4fe9e..6923b68b 100644 --- a/cmd/dendrite-demo-yggdrasil/main.go +++ b/cmd/dendrite-demo-yggdrasil/main.go @@ -94,18 +94,6 @@ func createFederationClient( func main() { flag.Parse() - // Build both ends of a HTTP multiplex. - httpServer := &http.Server{ - Addr: ":0", - TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){}, - ReadTimeout: 15 * time.Second, - WriteTimeout: 45 * time.Second, - IdleTimeout: 60 * time.Second, - BaseContext: func(_ net.Listener) context.Context { - return context.Background() - }, - } - ygg, err := yggconn.Setup(*instanceName, *instancePeer) if err != nil { panic(err) @@ -188,13 +176,26 @@ func main() { monolith.AddAllPublicRoutes(base.PublicAPIMux) httputil.SetupHTTPAPI( - http.DefaultServeMux, + base.BaseMux, base.PublicAPIMux, base.InternalAPIMux, cfg, base.UseHTTPAPIs, ) + // Build both ends of a HTTP multiplex. + httpServer := &http.Server{ + Addr: ":0", + TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){}, + ReadTimeout: 15 * time.Second, + WriteTimeout: 45 * time.Second, + IdleTimeout: 60 * time.Second, + BaseContext: func(_ net.Listener) context.Context { + return context.Background() + }, + Handler: base.BaseMux, + } + go func() { logrus.Info("Listening on ", ygg.DerivedServerName()) logrus.Fatal(httpServer.Serve(ygg)) @@ -202,7 +203,7 @@ func main() { go func() { httpBindAddr := fmt.Sprintf("localhost:%d", *instancePort) logrus.Info("Listening on ", httpBindAddr) - logrus.Fatal(http.ListenAndServe(httpBindAddr, nil)) + logrus.Fatal(http.ListenAndServe(httpBindAddr, base.BaseMux)) }() select {} diff --git a/cmd/dendrite-monolith-server/main.go b/cmd/dendrite-monolith-server/main.go index ea8160b8..d7b0bf48 100644 --- a/cmd/dendrite-monolith-server/main.go +++ b/cmd/dendrite-monolith-server/main.go @@ -135,7 +135,7 @@ func main() { monolith.AddAllPublicRoutes(base.PublicAPIMux) httputil.SetupHTTPAPI( - http.DefaultServeMux, + base.BaseMux, base.PublicAPIMux, base.InternalAPIMux, cfg, @@ -147,6 +147,7 @@ func main() { serv := http.Server{ Addr: *httpBindAddr, WriteTimeout: setup.HTTPServerTimeout, + Handler: base.BaseMux, } logrus.Info("Listening on ", serv.Addr) @@ -158,6 +159,7 @@ func main() { serv := http.Server{ Addr: *httpsBindAddr, WriteTimeout: setup.HTTPServerTimeout, + Handler: base.BaseMux, } logrus.Info("Listening on ", serv.Addr) diff --git a/cmd/dendritejs/jsServer.go b/cmd/dendritejs/jsServer.go index 31f12264..074d20cb 100644 --- a/cmd/dendritejs/jsServer.go +++ b/cmd/dendritejs/jsServer.go @@ -28,7 +28,7 @@ import ( // JSServer exposes an HTTP-like server interface which allows JS to 'send' requests to it. type JSServer struct { // The router which will service requests - Mux *http.ServeMux + Mux http.Handler } // OnRequestFromJS is the function that JS will invoke when there is a new request. diff --git a/cmd/dendritejs/main.go b/cmd/dendritejs/main.go index 70672f4d..8c19eb6d 100644 --- a/cmd/dendritejs/main.go +++ b/cmd/dendritejs/main.go @@ -19,7 +19,6 @@ package main import ( "crypto/ed25519" "fmt" - "net/http" "syscall/js" "github.com/matrix-org/dendrite/appservice" @@ -233,7 +232,7 @@ func main() { monolith.AddAllPublicRoutes(base.PublicAPIMux) httputil.SetupHTTPAPI( - http.DefaultServeMux, + base.BaseMux, base.PublicAPIMux, base.InternalAPIMux, cfg, @@ -245,7 +244,7 @@ func main() { go func() { logrus.Info("Listening on libp2p-js host ID ", node.Id) s := JSServer{ - Mux: http.DefaultServeMux, + Mux: base.BaseMux, } s.ListenAndServe("p2p") }() @@ -255,7 +254,7 @@ func main() { go func() { logrus.Info("Listening for service-worker fetch traffic") s := JSServer{ - Mux: http.DefaultServeMux, + Mux: base.BaseMux, } s.ListenAndServe("fetch") }() diff --git a/federationapi/federationapi.go b/federationapi/federationapi.go index 9299b501..db272f1c 100644 --- a/federationapi/federationapi.go +++ b/federationapi/federationapi.go @@ -35,7 +35,7 @@ func AddPublicRoutes( accountsDB accounts.Database, deviceDB devices.Database, federation *gomatrixserverlib.FederationClient, - keyRing *gomatrixserverlib.KeyRing, + keyRing gomatrixserverlib.JSONVerifier, rsAPI roomserverAPI.RoomserverInternalAPI, asAPI appserviceAPI.AppServiceQueryAPI, federationSenderAPI federationSenderAPI.FederationSenderInternalAPI, @@ -44,7 +44,7 @@ func AddPublicRoutes( routing.Setup( router, cfg, rsAPI, asAPI, - eduAPI, federationSenderAPI, *keyRing, + eduAPI, federationSenderAPI, keyRing, federation, accountsDB, deviceDB, ) } diff --git a/federationapi/federationapi_test.go b/federationapi/federationapi_test.go new file mode 100644 index 00000000..cf7d732b --- /dev/null +++ b/federationapi/federationapi_test.go @@ -0,0 +1,102 @@ +package federationapi_test + +import ( + "context" + "crypto/ed25519" + "strings" + "testing" + + "github.com/matrix-org/dendrite/federationapi" + "github.com/matrix-org/dendrite/internal/config" + "github.com/matrix-org/dendrite/internal/httputil" + "github.com/matrix-org/dendrite/internal/setup" + "github.com/matrix-org/dendrite/internal/test" + "github.com/matrix-org/gomatrix" + "github.com/matrix-org/gomatrixserverlib" +) + +// Tests that event IDs with '/' in them (escaped as %2F) are correctly passed to the right handler and don't 404. +// Relevant for v3 rooms and a cause of flakey sytests as the IDs are randomly generated. +func TestRoomsV3URLEscapeDoNot404(t *testing.T) { + _, privKey, _ := ed25519.GenerateKey(nil) + cfg := &config.Dendrite{} + cfg.Matrix.KeyID = gomatrixserverlib.KeyID("ed25519:auto") + cfg.Matrix.ServerName = gomatrixserverlib.ServerName("localhost") + cfg.Matrix.PrivateKey = privKey + cfg.Kafka.UseNaffka = true + cfg.Database.Naffka = "file::memory:" + cfg.SetDefaults() + base := setup.NewBaseDendrite(cfg, "Test", false) + keyRing := &test.NopJSONVerifier{} + fsAPI := base.FederationSenderHTTPClient() + // TODO: This is pretty fragile, as if anything calls anything on these nils this test will break. + // Unfortunately, it makes little sense to instantiate these dependencies when we just want to test routing. + federationapi.AddPublicRoutes(base.PublicAPIMux, cfg, nil, nil, nil, keyRing, nil, nil, fsAPI, nil) + httputil.SetupHTTPAPI( + base.BaseMux, + base.PublicAPIMux, + base.InternalAPIMux, + cfg, + base.UseHTTPAPIs, + ) + baseURL, cancel := test.ListenAndServe(t, base.BaseMux, true) + defer cancel() + serverName := gomatrixserverlib.ServerName(strings.TrimPrefix(baseURL, "https://")) + + fedCli := gomatrixserverlib.NewFederationClient(serverName, cfg.Matrix.KeyID, cfg.Matrix.PrivateKey) + + testCases := []struct { + roomVer gomatrixserverlib.RoomVersion + eventJSON string + }{ + { + eventJSON: `{"auth_events":[["$Nzfbrhc3oaYVKzGM:localhost",{"sha256":"BCBHOgB4qxLPQkBd6th8ydFSyqjth/LF99VNjYffOQ0"}],["$EZzkD2BH1Gtm5v1D:localhost",{"sha256":"3dLUnDBs8/iC5DMw/ydKtmAqVZtzqqtHpsjsQPk7GJA"}]],"content":{"body":"Test Message"},"depth":11,"event_id":"$mGiPO3oGjQfCkIUw:localhost","hashes":{"sha256":"h+t+4DwIBC9UNyJ3jzyAQAAl4H3yQHVuHrm2S1JZizU"},"origin":"localhost","origin_server_ts":0,"prev_events":[["$tFr64vpiSHdLU0Qr:localhost",{"sha256":"+R07ZrIs4c4tjPFE+tmcYIGUfeLGFI/4e0OITb9uEcM"}]],"room_id":"!roomid:localhost","sender":"@userid:localhost","signatures":{"localhost":{"ed25519:auto":"LYFr/rW9m5/7UKBQMF5qWnG82He4VGsRESUgDmvkn5DrJRyS4TLL/7zl0Lymn3pa3q2yaTO74LQX/CRotqG1BA"}},"type":"m.room.message"}`, + roomVer: gomatrixserverlib.RoomVersionV1, + }, + // single / (handlers which do not UseEncodedPath will fail this test) + // EventID: $0SFh2WJbjBs3OT+E0yl95giDKo/3Zp52HsHUUk4uPyg + { + eventJSON: `{"auth_events":["$x4MKEPRSF6OGlo0qpnsP3BfSmYX5HhVlykOsQH3ECyg","$BcEcbZnlFLB5rxSNSZNBn6fO3jU/TKAJ79wfKyCQLiU"],"content":{"body":"Test Message"},"depth":8,"hashes":{"sha256":"dfK0MBn1RZZqCVJqWsn/MGY7QJHjQcwqF0unOonLCTU"},"origin":"localhost","origin_server_ts":0,"prev_events":["$1SwcZ1XY/Y8yKLjP4DzAOHN5WFBcDAZxb5vFDnW2ubA"],"room_id":"!roomid:localhost","sender":"@userid:localhost","signatures":{"localhost":{"ed25519:auto":"INOjuWMg+GmFkUpmzhMB0bqLNs73mSvwldY1ftYIQ/B3lD9soD2OMG3AF+wgZW/I8xqzY4DOHfbnbUeYPf67BA"}},"type":"m.room.message"}`, + roomVer: gomatrixserverlib.RoomVersionV3, + }, + // multiple / + // EventID: $OzENBCuVv/fnRAYCeQudIon/84/V5pxtEjQMTgi3emk + { + eventJSON: `{"auth_events":["$x4MKEPRSF6OGlo0qpnsP3BfSmYX5HhVlykOsQH3ECyg","$BcEcbZnlFLB5rxSNSZNBn6fO3jU/TKAJ79wfKyCQLiU"],"content":{"body":"Test Message"},"depth":2,"hashes":{"sha256":"U5+WsiJAhiEM88J8HTjuUjPImVGVzDFD3v/WS+jb2f0"},"origin":"localhost","origin_server_ts":0,"prev_events":["$BcEcbZnlFLB5rxSNSZNBn6fO3jU/TKAJ79wfKyCQLiU"],"room_id":"!roomid:localhost","sender":"@userid:localhost","signatures":{"localhost":{"ed25519:auto":"tKS469e9+wdWPEKB/LbBJWQ8vfOOdKgTWER5IwbSAH1CxmLvkCziUsgVu85zfzDSLoUi5mU5FHLiMTC6P/qICw"}},"type":"m.room.message"}`, + roomVer: gomatrixserverlib.RoomVersionV3, + }, + // two slashes (handlers which clean paths before UseEncodedPath will fail this test) + // EventID: $EmwNBlHoSOVmCZ1cM//yv/OvxB6r4OFEIGSJea7+Amk + { + eventJSON: `{"auth_events":["$x4MKEPRSF6OGlo0qpnsP3BfSmYX5HhVlykOsQH3ECyg","$BcEcbZnlFLB5rxSNSZNBn6fO3jU/TKAJ79wfKyCQLiU"],"content":{"body":"Test Message"},"depth":3917,"hashes":{"sha256":"cNAWtlHIegrji0mMA6x1rhpYCccY8W1NsWZqSpJFhjs"},"origin":"localhost","origin_server_ts":0,"prev_events":["$4GDB0bVjkWwS3G4noUZCq5oLWzpBYpwzdMcf7gj24CI"],"room_id":"!roomid:localhost","sender":"@userid:localhost","signatures":{"localhost":{"ed25519:auto":"NKym6Kcy3u9mGUr21Hjfe3h7DfDilDhN5PqztT0QZ4NTZ+8Y7owseLolQVXp+TvNjecvzdDywsXXVvGiuQiWAQ"}},"type":"m.room.message"}`, + roomVer: gomatrixserverlib.RoomVersionV3, + }, + } + + for _, tc := range testCases { + ev, err := gomatrixserverlib.NewEventFromTrustedJSON([]byte(tc.eventJSON), false, tc.roomVer) + if err != nil { + t.Errorf("failed to parse event: %s", err) + } + he := ev.Headered(tc.roomVer) + invReq, err := gomatrixserverlib.NewInviteV2Request(&he, nil) + if err != nil { + t.Errorf("failed to create invite v2 request: %s", err) + continue + } + _, err = fedCli.SendInviteV2(context.Background(), serverName, invReq) + if err == nil { + t.Errorf("expected an error, got none") + continue + } + gerr, ok := err.(gomatrix.HTTPError) + if !ok { + t.Errorf("failed to cast response error as gomatrix.HTTPError") + continue + } + t.Logf("Error: %+v", gerr) + if gerr.Code == 404 { + t.Errorf("invite event resulted in a 404") + } + } +} diff --git a/federationapi/routing/invite.go b/federationapi/routing/invite.go index 908a04fc..7d02bc1d 100644 --- a/federationapi/routing/invite.go +++ b/federationapi/routing/invite.go @@ -35,7 +35,7 @@ func Invite( eventID string, cfg *config.Dendrite, rsAPI api.RoomserverInternalAPI, - keys gomatrixserverlib.KeyRing, + keys gomatrixserverlib.JSONVerifier, ) util.JSONResponse { inviteReq := gomatrixserverlib.InviteV2Request{} if err := json.Unmarshal(request.Content(), &inviteReq); err != nil { diff --git a/federationapi/routing/join.go b/federationapi/routing/join.go index e01f077a..8dcd1533 100644 --- a/federationapi/routing/join.go +++ b/federationapi/routing/join.go @@ -143,7 +143,7 @@ func SendJoin( request *gomatrixserverlib.FederationRequest, cfg *config.Dendrite, rsAPI api.RoomserverInternalAPI, - keys gomatrixserverlib.KeyRing, + keys gomatrixserverlib.JSONVerifier, roomID, eventID string, ) util.JSONResponse { verReq := api.QueryRoomVersionForRoomRequest{RoomID: roomID} diff --git a/federationapi/routing/leave.go b/federationapi/routing/leave.go index de15c32a..108fc50a 100644 --- a/federationapi/routing/leave.go +++ b/federationapi/routing/leave.go @@ -113,7 +113,7 @@ func SendLeave( request *gomatrixserverlib.FederationRequest, cfg *config.Dendrite, rsAPI api.RoomserverInternalAPI, - keys gomatrixserverlib.KeyRing, + keys gomatrixserverlib.JSONVerifier, roomID, eventID string, ) util.JSONResponse { verReq := api.QueryRoomVersionForRoomRequest{RoomID: roomID} diff --git a/federationapi/routing/routing.go b/federationapi/routing/routing.go index 70b77a4c..350febbc 100644 --- a/federationapi/routing/routing.go +++ b/federationapi/routing/routing.go @@ -51,7 +51,7 @@ func Setup( asAPI appserviceAPI.AppServiceQueryAPI, eduAPI eduserverAPI.EDUServerInputAPI, fsAPI federationSenderAPI.FederationSenderInternalAPI, - keys gomatrixserverlib.KeyRing, + keys gomatrixserverlib.JSONVerifier, federation *gomatrixserverlib.FederationClient, accountDB accounts.Database, deviceDB devices.Database, diff --git a/federationapi/routing/send.go b/federationapi/routing/send.go index a057750f..cf71b8ba 100644 --- a/federationapi/routing/send.go +++ b/federationapi/routing/send.go @@ -37,7 +37,7 @@ func Send( cfg *config.Dendrite, rsAPI api.RoomserverInternalAPI, eduAPI eduserverAPI.EDUServerInputAPI, - keys gomatrixserverlib.KeyRing, + keys gomatrixserverlib.JSONVerifier, federation *gomatrixserverlib.FederationClient, ) util.JSONResponse { t := txnReq{ diff --git a/federationapi/routing/send_test.go b/federationapi/routing/send_test.go index 3123b55c..6e6606c8 100644 --- a/federationapi/routing/send_test.go +++ b/federationapi/routing/send_test.go @@ -10,6 +10,7 @@ import ( eduAPI "github.com/matrix-org/dendrite/eduserver/api" fsAPI "github.com/matrix-org/dendrite/federationsender/api" + "github.com/matrix-org/dendrite/internal/test" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/gomatrixserverlib" ) @@ -53,15 +54,6 @@ func init() { } } -type testNopJSONVerifier struct { - // this verifier verifies nothing -} - -func (t *testNopJSONVerifier) VerifyJSONs(ctx context.Context, requests []gomatrixserverlib.VerifyJSONRequest) ([]gomatrixserverlib.VerifyJSONResult, error) { - result := make([]gomatrixserverlib.VerifyJSONResult, len(requests)) - return result, nil -} - type testEDUProducer struct { // this producer keeps track of calls to InputTypingEvent invocations []eduAPI.InputTypingEventRequest @@ -330,7 +322,7 @@ func mustCreateTransaction(rsAPI api.RoomserverInternalAPI, fedClient txnFederat context: context.Background(), rsAPI: rsAPI, eduAPI: &testEDUProducer{}, - keys: &testNopJSONVerifier{}, + keys: &test.NopJSONVerifier{}, federation: fedClient, haveEvents: make(map[string]*gomatrixserverlib.HeaderedEvent), newEvents: make(map[string]bool), diff --git a/internal/httputil/httpapi.go b/internal/httputil/httpapi.go index 0a37f06c..a35a10d6 100644 --- a/internal/httputil/httpapi.go +++ b/internal/httputil/httpapi.go @@ -185,7 +185,7 @@ func MakeInternalAPI(metricsName string, f func(*http.Request) util.JSONResponse func MakeFedAPI( metricsName string, serverName gomatrixserverlib.ServerName, - keyRing gomatrixserverlib.KeyRing, + keyRing gomatrixserverlib.JSONVerifier, wakeup *FederationWakeups, f func(*http.Request, *gomatrixserverlib.FederationRequest, map[string]string) util.JSONResponse, ) http.Handler { @@ -233,9 +233,8 @@ func (f *FederationWakeups) Wakeup(ctx context.Context, origin gomatrixserverlib } } -// SetupHTTPAPI registers an HTTP API mux under /api and sets up a metrics -// listener. -func SetupHTTPAPI(servMux *http.ServeMux, publicApiMux *mux.Router, internalApiMux *mux.Router, cfg *config.Dendrite, enableHTTPAPIs bool) { +// SetupHTTPAPI registers an HTTP API mux under /api and sets up a metrics listener +func SetupHTTPAPI(servMux, publicApiMux, internalApiMux *mux.Router, cfg *config.Dendrite, enableHTTPAPIs bool) { if cfg.Metrics.Enabled { servMux.Handle("/metrics", WrapHandlerInBasicAuth(promhttp.Handler(), cfg.Metrics.BasicAuth)) } diff --git a/internal/setup/base.go b/internal/setup/base.go index fb304893..414b7964 100644 --- a/internal/setup/base.go +++ b/internal/setup/base.go @@ -63,6 +63,7 @@ type BaseDendrite struct { // PublicAPIMux should be used to register new public matrix api endpoints PublicAPIMux *mux.Router InternalAPIMux *mux.Router + BaseMux *mux.Router // base router which created public/internal subrouters UseHTTPAPIs bool httpClient *http.Client Cfg *config.Dendrite @@ -127,6 +128,7 @@ func NewBaseDendrite(cfg *config.Dendrite, componentName string, useHTTPAPIs boo tracerCloser: closer, Cfg: cfg, Caches: cache, + BaseMux: httpmux, PublicAPIMux: httpmux.PathPrefix(httputil.PublicPathPrefix).Subrouter().UseEncodedPath(), InternalAPIMux: httpmux.PathPrefix(httputil.InternalPathPrefix).Subrouter().UseEncodedPath(), httpClient: &client, @@ -238,12 +240,13 @@ func (b *BaseDendrite) SetupAndServeHTTP(bindaddr string, listenaddr string) { } httputil.SetupHTTPAPI( - http.DefaultServeMux, + b.BaseMux, b.PublicAPIMux, b.InternalAPIMux, b.Cfg, b.UseHTTPAPIs, ) + serv.Handler = b.BaseMux logrus.Infof("Starting %s server on %s", b.componentName, serv.Addr) err := serv.ListenAndServe() diff --git a/internal/test/keyring.go b/internal/test/keyring.go new file mode 100644 index 00000000..ed9c3484 --- /dev/null +++ b/internal/test/keyring.go @@ -0,0 +1,31 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "context" + + "github.com/matrix-org/gomatrixserverlib" +) + +// NopJSONVerifier is a JSONVerifier that verifies nothing and returns no errors. +type NopJSONVerifier struct { + // this verifier verifies nothing +} + +func (t *NopJSONVerifier) VerifyJSONs(ctx context.Context, requests []gomatrixserverlib.VerifyJSONRequest) ([]gomatrixserverlib.VerifyJSONResult, error) { + result := make([]gomatrixserverlib.VerifyJSONResult, len(requests)) + return result, nil +} diff --git a/internal/test/server.go b/internal/test/server.go index 1493dac6..c3348d53 100644 --- a/internal/test/server.go +++ b/internal/test/server.go @@ -1,4 +1,4 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,11 +15,16 @@ package test import ( + "context" "fmt" + "net" + "net/http" "os" "os/exec" "path/filepath" "strings" + "sync" + "testing" "github.com/matrix-org/dendrite/internal/config" ) @@ -103,3 +108,46 @@ func StartProxy(bindAddr string, cfg *config.Dendrite) (*exec.Cmd, chan error) { proxyArgs, ) } + +// ListenAndServe will listen on a random high-numbered port and attach the given router. +// Returns the base URL to send requests to. Call `cancel` to shutdown the server, which will block until it has closed. +func ListenAndServe(t *testing.T, router http.Handler, useTLS bool) (apiURL string, cancel func()) { + listener, err := net.Listen("tcp", ":0") + if err != nil { + t.Fatalf("failed to listen: %s", err) + } + port := listener.Addr().(*net.TCPAddr).Port + srv := http.Server{} + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + srv.Handler = router + var err error + if useTLS { + certFile := filepath.Join(os.TempDir(), "dendrite.cert") + keyFile := filepath.Join(os.TempDir(), "dendrite.key") + err = NewTLSKey(keyFile, certFile) + if err != nil { + t.Logf("failed to generate tls key/cert: %s", err) + return + } + err = srv.ServeTLS(listener, certFile, keyFile) + } else { + err = srv.Serve(listener) + } + if err != nil && err != http.ErrServerClosed { + t.Logf("Listen failed: %s", err) + } + }() + + secure := "" + if useTLS { + secure = "s" + } + return fmt.Sprintf("http%s://localhost:%d", secure, port), func() { + _ = srv.Shutdown(context.Background()) + wg.Wait() + } +} diff --git a/userapi/userapi_test.go b/userapi/userapi_test.go index 423a8612..a4616363 100644 --- a/userapi/userapi_test.go +++ b/userapi/userapi_test.go @@ -3,16 +3,15 @@ package userapi_test import ( "context" "fmt" - "net" "net/http" "reflect" - "sync" "testing" "github.com/gorilla/mux" "github.com/matrix-org/dendrite/clientapi/auth/storage/accounts" "github.com/matrix-org/dendrite/clientapi/auth/storage/devices" "github.com/matrix-org/dendrite/internal/httputil" + "github.com/matrix-org/dendrite/internal/test" "github.com/matrix-org/dendrite/userapi" "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/dendrite/userapi/inthttp" @@ -99,7 +98,7 @@ func TestQueryProfile(t *testing.T) { t.Run("HTTP API", func(t *testing.T) { router := mux.NewRouter().PathPrefix(httputil.InternalPathPrefix).Subrouter() userapi.AddInternalRoutes(router, userAPI) - apiURL, cancel := listenAndServe(t, router) + apiURL, cancel := test.ListenAndServe(t, router, false) defer cancel() httpAPI, err := inthttp.NewUserAPIClient(apiURL, &http.Client{}) if err != nil { @@ -111,28 +110,3 @@ func TestQueryProfile(t *testing.T) { runCases(userAPI) }) } - -func listenAndServe(t *testing.T, router *mux.Router) (apiURL string, cancel func()) { - listener, err := net.Listen("tcp", ":0") - if err != nil { - t.Fatalf("failed to listen: %s", err) - } - port := listener.Addr().(*net.TCPAddr).Port - srv := http.Server{} - - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - srv.Handler = router - err := srv.Serve(listener) - if err != nil && err != http.ErrServerClosed { - t.Logf("Listen failed: %s", err) - } - }() - - return fmt.Sprintf("http://localhost:%d", port), func() { - srv.Shutdown(context.Background()) - wg.Wait() - } -}