From af6bc47f1654cfcb6b4834da08308fe4c6291d36 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 8 Jul 2020 14:52:48 +0100 Subject: [PATCH 01/15] Squashed commit of the following: commit b4cb47aa1329d2ada10ae6426fd9d2a69f47536a Author: Neil Alexander Date: Wed Jul 8 14:13:27 2020 +0100 Restrict transaction send context time commit 7c28205cdb5d842071d46b1ec599d09cca708e57 Author: Neil Alexander Date: Wed Jul 8 14:00:06 2020 +0100 Add to gobind build commit d9e2c72e0576a2eb0ce6ac48eed6cc9d4761a0ea Author: Neil Alexander Date: Wed Jul 8 13:43:21 2020 +0100 Wake up destination queues for new sessions/links commit 21766c6c52bd00511d28981457e9034358c32a8d Author: Neil Alexander Date: Wed Jul 8 13:17:18 2020 +0100 Tweak QUIC parameters --- build/gobind/monolith.go | 28 +++++++++++++++++++++ cmd/dendrite-demo-yggdrasil/main.go | 26 +++++++++++++++++++ cmd/dendrite-demo-yggdrasil/yggconn/node.go | 21 ++++++++++++++-- federationsender/queue/destinationqueue.go | 6 +++-- go.mod | 3 ++- go.sum | 21 ++++++++++++++++ 6 files changed, 100 insertions(+), 5 deletions(-) diff --git a/build/gobind/monolith.go b/build/gobind/monolith.go index 5bf097e87..5f4c5513b 100644 --- a/build/gobind/monolith.go +++ b/build/gobind/monolith.go @@ -16,6 +16,7 @@ import ( "github.com/matrix-org/dendrite/eduserver" "github.com/matrix-org/dendrite/eduserver/cache" "github.com/matrix-org/dendrite/federationsender" + "github.com/matrix-org/dendrite/federationsender/api" "github.com/matrix-org/dendrite/internal/config" "github.com/matrix-org/dendrite/internal/httputil" "github.com/matrix-org/dendrite/internal/setup" @@ -23,6 +24,7 @@ import ( "github.com/matrix-org/dendrite/userapi" "github.com/matrix-org/gomatrixserverlib" "github.com/sirupsen/logrus" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) type DendriteMonolith struct { @@ -158,6 +160,32 @@ func (m *DendriteMonolith) Start() { base.UseHTTPAPIs, ) + ygg.NotifySessionNew(func(boxPubKey crypto.BoxPubKey) { + serv := gomatrixserverlib.ServerName(boxPubKey.String()) + req := &api.PerformServersAliveRequest{ + Servers: []gomatrixserverlib.ServerName{serv}, + } + res := &api.PerformServersAliveResponse{} + if err := fsAPI.PerformServersAlive(context.TODO(), req, res); err != nil { + logrus.WithError(err).Warnf("Failed to notify server %q alive due to new session", serv) + } else { + logrus.Infof("Notified server %q alive due to new session", serv) + } + }) + + ygg.NotifyLinkNew(func(boxPubKey crypto.BoxPubKey, linkType, remote string) { + serv := gomatrixserverlib.ServerName(boxPubKey.String()) + req := &api.PerformServersAliveRequest{ + Servers: []gomatrixserverlib.ServerName{serv}, + } + res := &api.PerformServersAliveResponse{} + if err := fsAPI.PerformServersAlive(context.TODO(), req, res); err != nil { + logrus.WithError(err).Warnf("Failed to notify server %q alive due to new peer", serv) + } else { + logrus.Infof("Notified server %q alive due to new peer", serv) + } + }) + // Build both ends of a HTTP multiplex. httpServer := &http.Server{ Addr: ":0", diff --git a/cmd/dendrite-demo-yggdrasil/main.go b/cmd/dendrite-demo-yggdrasil/main.go index b2d40f4f7..f64bb2513 100644 --- a/cmd/dendrite-demo-yggdrasil/main.go +++ b/cmd/dendrite-demo-yggdrasil/main.go @@ -32,6 +32,7 @@ import ( "github.com/matrix-org/dendrite/eduserver" "github.com/matrix-org/dendrite/eduserver/cache" "github.com/matrix-org/dendrite/federationsender" + "github.com/matrix-org/dendrite/federationsender/api" "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/internal/config" "github.com/matrix-org/dendrite/internal/httputil" @@ -39,6 +40,7 @@ import ( "github.com/matrix-org/dendrite/roomserver" "github.com/matrix-org/dendrite/userapi" "github.com/matrix-org/gomatrixserverlib" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/sirupsen/logrus" ) @@ -150,6 +152,30 @@ func main() { base.UseHTTPAPIs, ) + ygg.NotifySessionNew(func(boxPubKey crypto.BoxPubKey) { + req := &api.PerformServersAliveRequest{ + Servers: []gomatrixserverlib.ServerName{ + gomatrixserverlib.ServerName(boxPubKey.String()), + }, + } + res := &api.PerformServersAliveResponse{} + if err := fsAPI.PerformServersAlive(context.TODO(), req, res); err != nil { + logrus.WithError(err).Warn("Failed to notify server alive due to new session") + } + }) + + ygg.NotifyLinkNew(func(boxPubKey crypto.BoxPubKey, linkType, remote string) { + req := &api.PerformServersAliveRequest{ + Servers: []gomatrixserverlib.ServerName{ + gomatrixserverlib.ServerName(boxPubKey.String()), + }, + } + res := &api.PerformServersAliveResponse{} + if err := fsAPI.PerformServersAlive(context.TODO(), req, res); err != nil { + logrus.WithError(err).Warn("Failed to notify server alive due to new link") + } + }) + // Build both ends of a HTTP multiplex. httpServer := &http.Server{ Addr: ":0", diff --git a/cmd/dendrite-demo-yggdrasil/yggconn/node.go b/cmd/dendrite-demo-yggdrasil/yggconn/node.go index 140e582cb..f471289eb 100644 --- a/cmd/dendrite-demo-yggdrasil/yggconn/node.go +++ b/cmd/dendrite-demo-yggdrasil/yggconn/node.go @@ -35,6 +35,7 @@ import ( yggdrasiladmin "github.com/yggdrasil-network/yggdrasil-go/src/admin" yggdrasilconfig "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" yggdrasilmulticast "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" @@ -135,8 +136,8 @@ func Setup(instanceName, storageDirectory string) (*Node, error) { n.quicConfig = &quic.Config{ MaxIncomingStreams: 0, MaxIncomingUniStreams: 0, - KeepAlive: true, - MaxIdleTimeout: time.Second * 900, + KeepAlive: false, + MaxIdleTimeout: time.Second * 60, HandshakeTimeout: time.Second * 30, } @@ -262,3 +263,19 @@ func (n *Node) SetStaticPeer(uri string) error { } return nil } + +func (n *Node) NotifyLinkNew(f func(boxPubKey crypto.BoxPubKey, linkType, remote string)) { + n.core.NotifyLinkNew(f) +} + +func (n *Node) NotifyLinkGone(f func(boxPubKey crypto.BoxPubKey, linkType, remote string)) { + n.core.NotifyLinkGone(f) +} + +func (n *Node) NotifySessionNew(f func(boxPubKey crypto.BoxPubKey)) { + n.core.NotifySessionNew(f) +} + +func (n *Node) NotifySessionGone(f func(boxPubKey crypto.BoxPubKey)) { + n.core.NotifySessionGone(f) +} diff --git a/federationsender/queue/destinationqueue.go b/federationsender/queue/destinationqueue.go index d9d531887..61819deb4 100644 --- a/federationsender/queue/destinationqueue.go +++ b/federationsender/queue/destinationqueue.go @@ -381,7 +381,9 @@ func (oq *destinationQueue) nextTransaction( // TODO: we should check for 500-ish fails vs 400-ish here, // since we shouldn't queue things indefinitely in response // to a 400-ish error - _, err = oq.client.SendTransaction(context.TODO(), t) + ctx, cancel = context.WithTimeout(context.Background(), time.Second*15) + defer cancel() + _, err = oq.client.SendTransaction(ctx, t) switch err.(type) { case nil: // No error was returned so the transaction looks to have @@ -389,7 +391,7 @@ func (oq *destinationQueue) nextTransaction( oq.pendingPDUs.Sub(int64(len(t.PDUs))) // Clean up the transaction in the database. if err = oq.db.CleanTransactionPDUs( - context.TODO(), + context.Background(), t.Destination, t.TransactionID, ); err != nil { diff --git a/go.mod b/go.mod index 4efd0bca1..b007b09ad 100644 --- a/go.mod +++ b/go.mod @@ -36,9 +36,10 @@ require ( github.com/uber-go/atomic v1.3.0 // indirect github.com/uber/jaeger-client-go v2.15.0+incompatible github.com/uber/jaeger-lib v1.5.0 - github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200707124509-16343a00055c + github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200708124809-79077e271c6d go.uber.org/atomic v1.4.0 golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 + golang.org/x/mobile v0.0.0-20200629153529-33b80540585f // indirect gopkg.in/h2non/bimg.v1 v1.0.18 gopkg.in/yaml.v2 v2.2.8 ) diff --git a/go.sum b/go.sum index 586fa39d4..2b4644b67 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,7 @@ github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOv github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0 h1:p3puK8Sl2xK+2FnnIvY/C0N1aqJo2kbEsdAzU+Tnv48= github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/Shopify/sarama v1.26.1 h1:3jnfWKD7gVwbB1KSy/lE0szA9duPuSFLViK0o/d3DgA= github.com/Shopify/sarama v1.26.1/go.mod h1:NbSGBSSndYaIhRcBtY9V0U7AyH+x71bG668AuWys/yU= @@ -654,6 +655,12 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1: github.com/yggdrasil-network/yggdrasil-extras v0.0.0-20200525205615-6c8a4a2e8855/go.mod h1:xQdsh08Io6nV4WRnOVTe6gI8/2iTvfLDQ0CYa5aMt+I= github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200707124509-16343a00055c h1:tK1FySfDA5xVT5sAK/3XjUCE9LEoandmVnrg4Hj0fXk= github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200707124509-16343a00055c/go.mod h1:d+Nz6SPeG6kmeSPFL0cvfWfgwEql75fUnZiAONgvyBE= +github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200708120322-c6245869aeac h1:TMehvDn6TmWemBMxBFQoF/rQ513OmytfDY6sLPz3M64= +github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200708120322-c6245869aeac/go.mod h1:d+Nz6SPeG6kmeSPFL0cvfWfgwEql75fUnZiAONgvyBE= +github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200708123331-4e0b0e723459 h1:B7wypB+eA3xdB9bH/rFNywP3vg0MuIoMb3SojWApt+4= +github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200708123331-4e0b0e723459/go.mod h1:d+Nz6SPeG6kmeSPFL0cvfWfgwEql75fUnZiAONgvyBE= +github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200708124809-79077e271c6d h1:ly327dysc3r7lfG+AKJWPSAQmGf4h++fk+Y2dD8nDV4= +github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200708124809-79077e271c6d/go.mod h1:d+Nz6SPeG6kmeSPFL0cvfWfgwEql75fUnZiAONgvyBE= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= @@ -679,6 +686,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90Pveol golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -691,10 +699,19 @@ golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzhtallWRSm4Q0d09pL6XbQtU= golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20200629153529-33b80540585f h1:9MxnlCHwn6IfUTinHBBzcBhmrX4OXfRmi954tWGKq+M= +golang.org/x/mobile v0.0.0-20200629153529-33b80540585f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -781,7 +798,11 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd h1:/e+gpKk9r3dJobndpTytxS2gOy6m5uvpg+ISQoEcusQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk= +golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= From 2bb580c1b09fb927bdd213f8783dc6fa1d7c25f1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 8 Jul 2020 15:42:36 +0100 Subject: [PATCH 02/15] Handle case where pendingPDUs might get out of sync for some reason --- federationsender/queue/destinationqueue.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/federationsender/queue/destinationqueue.go b/federationsender/queue/destinationqueue.go index 61819deb4..7751326c3 100644 --- a/federationsender/queue/destinationqueue.go +++ b/federationsender/queue/destinationqueue.go @@ -349,7 +349,17 @@ func (oq *destinationQueue) nextTransaction( // If we didn't get anything from the database and there are no // pending EDUs then there's nothing to do - stop here. if len(pdus) == 0 && len(pendingEDUs) == 0 { - log.Warnf("no pdus/edus for nextTransaction for destination %q", oq.destination) + log.Warnf("Expected PDUs/EDUs for destination %q but got none", oq.destination) + // This shouldn't really happen but since it has, let's check + // how many events are *really* in the database that are waiting. + if count, cerr := oq.db.GetPendingPDUCount( + context.TODO(), + oq.destination, + ); cerr == nil { + oq.pendingPDUs.Store(count) + } else { + log.Warnf("Failed to retrieve pending PDU count for %q", oq.destination) + } return false, nil } From 689c3df02dc0d499371625614231ea7997707307 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 8 Jul 2020 15:44:37 +0100 Subject: [PATCH 03/15] Set QUIC keepalive again --- cmd/dendrite-demo-yggdrasil/yggconn/node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/dendrite-demo-yggdrasil/yggconn/node.go b/cmd/dendrite-demo-yggdrasil/yggconn/node.go index f471289eb..79a56c8ef 100644 --- a/cmd/dendrite-demo-yggdrasil/yggconn/node.go +++ b/cmd/dendrite-demo-yggdrasil/yggconn/node.go @@ -136,7 +136,7 @@ func Setup(instanceName, storageDirectory string) (*Node, error) { n.quicConfig = &quic.Config{ MaxIncomingStreams: 0, MaxIncomingUniStreams: 0, - KeepAlive: false, + KeepAlive: true, MaxIdleTimeout: time.Second * 60, HandshakeTimeout: time.Second * 30, } From fbd9c11c3c17da3da62659e7295fd0b1421746bb Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 8 Jul 2020 15:48:10 +0100 Subject: [PATCH 04/15] Check error when setting static peer --- cmd/dendrite-demo-yggdrasil/main.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/dendrite-demo-yggdrasil/main.go b/cmd/dendrite-demo-yggdrasil/main.go index f64bb2513..84e2dbd03 100644 --- a/cmd/dendrite-demo-yggdrasil/main.go +++ b/cmd/dendrite-demo-yggdrasil/main.go @@ -62,7 +62,9 @@ func main() { } ygg.SetMulticastEnabled(true) if instancePeer != nil && *instancePeer != "" { - ygg.SetStaticPeer(*instancePeer) + if err := ygg.SetStaticPeer(*instancePeer); err != nil { + logrus.WithError(err).Error("Failed to set static peer") + } } cfg := &config.Dendrite{} From 4370a808e394e23f36dfd61fd82bba95720ae075 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 8 Jul 2020 16:24:31 +0100 Subject: [PATCH 05/15] Fix media path for iOS gobind build --- build/gobind/monolith.go | 2 ++ cmd/dendrite-demo-yggdrasil/yggconn/node.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/build/gobind/monolith.go b/build/gobind/monolith.go index 5f4c5513b..d220fabc4 100644 --- a/build/gobind/monolith.go +++ b/build/gobind/monolith.go @@ -95,6 +95,8 @@ func (m *DendriteMonolith) Start() { cfg.Database.AppService = config.DataSource(fmt.Sprintf("file:%s/dendrite-appservice.db", m.StorageDirectory)) cfg.Database.CurrentState = config.DataSource(fmt.Sprintf("file:%s/dendrite-currentstate.db", m.StorageDirectory)) cfg.Database.Naffka = config.DataSource(fmt.Sprintf("file:%s/dendrite-naffka.db", m.StorageDirectory)) + cfg.Media.BasePath = config.Path(fmt.Sprintf("%s/tmp", m.StorageDirectory)) + cfg.Media.AbsBasePath = config.Path(fmt.Sprintf("%s/tmp", m.StorageDirectory)) if err = cfg.Derive(); err != nil { panic(err) } diff --git a/cmd/dendrite-demo-yggdrasil/yggconn/node.go b/cmd/dendrite-demo-yggdrasil/yggconn/node.go index 79a56c8ef..89fb69b53 100644 --- a/cmd/dendrite-demo-yggdrasil/yggconn/node.go +++ b/cmd/dendrite-demo-yggdrasil/yggconn/node.go @@ -138,7 +138,7 @@ func Setup(instanceName, storageDirectory string) (*Node, error) { MaxIncomingUniStreams: 0, KeepAlive: true, MaxIdleTimeout: time.Second * 60, - HandshakeTimeout: time.Second * 30, + HandshakeTimeout: time.Second * 15, } n.log.Println("Public curve25519:", n.core.EncryptionPublicKey()) From 6bee6e77d97ff1413bccc759385a68a90421ac8c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 8 Jul 2020 16:39:50 +0100 Subject: [PATCH 06/15] Yet another v1 vs r0 media endpoint (#1190) --- mediaapi/routing/routing.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mediaapi/routing/routing.go b/mediaapi/routing/routing.go index bc0de0f45..f4a8b1575 100644 --- a/mediaapi/routing/routing.go +++ b/mediaapi/routing/routing.go @@ -53,12 +53,16 @@ func Setup( activeThumbnailGeneration := &types.ActiveThumbnailGeneration{ PathToResult: map[string]*types.ThumbnailGenerationResult{}, } - r0mux.Handle("/upload", httputil.MakeAuthAPI( + + uploadHandler := httputil.MakeAuthAPI( "upload", userAPI, func(req *http.Request, _ *userapi.Device) util.JSONResponse { return Upload(req, cfg, db, activeThumbnailGeneration) }, - )).Methods(http.MethodPost, http.MethodOptions) + ) + + r0mux.Handle("/upload", uploadHandler).Methods(http.MethodPost, http.MethodOptions) + v1mux.Handle("/upload", uploadHandler).Methods(http.MethodPost, http.MethodOptions) activeRemoteRequests := &types.ActiveRemoteRequests{ MXCToResult: map[string]*types.RemoteRequestResult{}, From a5a51b41416e9b1d8084dbc759dff735133817fa Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 8 Jul 2020 17:28:16 +0100 Subject: [PATCH 07/15] linter --- cmd/dendrite-demo-yggdrasil/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/dendrite-demo-yggdrasil/main.go b/cmd/dendrite-demo-yggdrasil/main.go index 84e2dbd03..657477563 100644 --- a/cmd/dendrite-demo-yggdrasil/main.go +++ b/cmd/dendrite-demo-yggdrasil/main.go @@ -62,7 +62,7 @@ func main() { } ygg.SetMulticastEnabled(true) if instancePeer != nil && *instancePeer != "" { - if err := ygg.SetStaticPeer(*instancePeer); err != nil { + if err = ygg.SetStaticPeer(*instancePeer); err != nil { logrus.WithError(err).Error("Failed to set static peer") } } From d9648b0615f3a7b1d8a824777783f19fa46697f4 Mon Sep 17 00:00:00 2001 From: Kegsay Date: Wed, 8 Jul 2020 17:45:39 +0100 Subject: [PATCH 08/15] Finish implementing redactions (#1189) * Add a bit more logging to the fedsender * bugfix: continue sending PDUs if ones are added whilst sending another PDU Without this, the queue goes back to sleep on `<-oq.notifyPDUs` which won't fire because `pendingPDUs` is already > 0. This should fix a flakey sytest. * Break if no txn is sent * WIP syncapi work * More debugging * Bump GMSL version to pull in working Event.Redact * Remove logging * Make redactions work on v3+ * Fix more tests --- currentstateserver/storage/shared/storage.go | 6 +--- go.mod | 2 +- go.sum | 6 ++-- internal/eventutil/events.go | 5 +++ roomserver/api/output.go | 16 +++++++++ roomserver/internal/query.go | 11 +++--- roomserver/storage/shared/storage.go | 4 +++ syncapi/consumers/roomserver.go | 34 ++++++++++++++++--- syncapi/storage/interface.go | 2 ++ .../postgres/output_room_events_table.go | 16 +++++++++ syncapi/storage/shared/syncserver.go | 21 ++++++++++++ .../sqlite3/output_room_events_table.go | 16 +++++++++ syncapi/storage/tables/interface.go | 1 + sytest-whitelist | 16 ++++----- 14 files changed, 131 insertions(+), 25 deletions(-) diff --git a/currentstateserver/storage/shared/storage.go b/currentstateserver/storage/shared/storage.go index 362dafe96..66b979d88 100644 --- a/currentstateserver/storage/shared/storage.go +++ b/currentstateserver/storage/shared/storage.go @@ -23,7 +23,6 @@ import ( "github.com/matrix-org/dendrite/internal/eventutil" "github.com/matrix-org/dendrite/internal/sqlutil" "github.com/matrix-org/gomatrixserverlib" - "github.com/matrix-org/util" ) type Database struct { @@ -45,10 +44,7 @@ func (d *Database) RedactEvent(ctx context.Context, redactedEventID string, reda return err } if len(events) != 1 { - // this should never happen but is non-fatal - util.GetLogger(ctx).WithField("redacted_event_id", redactedEventID).WithField("redaction_event_id", redactedBecause.EventID()).Warnf( - "RedactEvent: missing redacted event", - ) + // this will happen for all non-state events return nil } redactionEvent := redactedBecause.Unwrap() diff --git a/go.mod b/go.mod index b007b09ad..5c896a377 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/matrix-org/go-http-js-libp2p v0.0.0-20200518170932-783164aeeda4 github.com/matrix-org/go-sqlite3-js v0.0.0-20200522092705-bc8506ccbcf3 github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26 - github.com/matrix-org/gomatrixserverlib v0.0.0-20200707103800-7470b03f069b + github.com/matrix-org/gomatrixserverlib v0.0.0-20200708152912-d034ccb75e2d github.com/matrix-org/naffka v0.0.0-20200422140631-181f1ee7401f github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7 github.com/mattn/go-sqlite3 v2.0.2+incompatible diff --git a/go.sum b/go.sum index 2b4644b67..1fd01ce9e 100644 --- a/go.sum +++ b/go.sum @@ -422,8 +422,10 @@ github.com/matrix-org/go-sqlite3-js v0.0.0-20200522092705-bc8506ccbcf3 h1:Yb+Wlf github.com/matrix-org/go-sqlite3-js v0.0.0-20200522092705-bc8506ccbcf3/go.mod h1:e+cg2q7C7yE5QnAXgzo512tgFh1RbQLC0+jozuegKgo= github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26 h1:Hr3zjRsq2bhrnp3Ky1qgx/fzCtCALOoGYylh2tpS9K4= github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0= -github.com/matrix-org/gomatrixserverlib v0.0.0-20200707103800-7470b03f069b h1:g1ueoPHI5tpafw/QysVfDw43FwRTPqz8sT+MZbK54yk= -github.com/matrix-org/gomatrixserverlib v0.0.0-20200707103800-7470b03f069b/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= +github.com/matrix-org/gomatrixserverlib v0.0.0-20200708143827-8bfb7222929b h1:mHFK2pcy+fhettE42aoq+JTcj3ysqFqkQLlUpC33Fwg= +github.com/matrix-org/gomatrixserverlib v0.0.0-20200708143827-8bfb7222929b/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= +github.com/matrix-org/gomatrixserverlib v0.0.0-20200708152912-d034ccb75e2d h1:v3MGEwCLlFjzZcYJu+aI3kUoNxQyCM3DUurjJFlaI04= +github.com/matrix-org/gomatrixserverlib v0.0.0-20200708152912-d034ccb75e2d/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= github.com/matrix-org/naffka v0.0.0-20200422140631-181f1ee7401f h1:pRz4VTiRCO4zPlEMc3ESdUOcW4PXHH4Kj+YDz1XyE+Y= github.com/matrix-org/naffka v0.0.0-20200422140631-181f1ee7401f/go.mod h1:y0oDTjZDv5SM9a2rp3bl+CU+bvTRINQsdb7YlDql5Go= github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7 h1:ntrLa/8xVzeSs8vHFHK25k0C+NV74sYMJnNSg5NoSRo= diff --git a/internal/eventutil/events.go b/internal/eventutil/events.go index e3b8f3d31..1e3afac8a 100644 --- a/internal/eventutil/events.go +++ b/internal/eventutil/events.go @@ -162,5 +162,10 @@ func RedactEvent(redactionEvent, redactedEvent *gomatrixserverlib.Event) (*gomat if err != nil { return nil, err } + // NOTSPEC: sytest relies on this unspecced field existing :( + err = r.SetUnsignedField("redacted_by", redactionEvent.EventID()) + if err != nil { + return nil, err + } return &r, nil } diff --git a/roomserver/api/output.go b/roomserver/api/output.go index b25353ae4..d6c09f9e8 100644 --- a/roomserver/api/output.go +++ b/roomserver/api/output.go @@ -29,6 +29,22 @@ const ( // OutputTypeRetireInviteEvent indicates that the event is an OutputRetireInviteEvent OutputTypeRetireInviteEvent OutputType = "retire_invite_event" // OutputTypeRedactedEvent indicates that the event is an OutputRedactedEvent + // + // This event is emitted when a redaction has been 'validated' (meaning both the redaction and the event to redact are known). + // Redaction validation happens when the roomserver receives either: + // - A redaction for which we have the event to redact. + // - Any event for which we have a redaction. + // When the roomserver receives an event, it will check against the redactions table to see if there is a matching redaction + // for the event. If there is, it will mark the redaction as validated and emit this event. In the common case of a redaction + // happening after receiving the event to redact, the roomserver will emit a OutputTypeNewRoomEvent of m.room.redaction + // immediately followed by a OutputTypeRedactedEvent. In the uncommon case of receiving the redaction BEFORE the event to redact, + // the roomserver will emit a OutputTypeNewRoomEvent of the event to redact immediately followed by a OutputTypeRedactedEvent. + // + // In order to honour redactions correctly, downstream components must ignore m.room.redaction events emitted via OutputTypeNewRoomEvent. + // When downstream components receive an OutputTypeRedactedEvent they must: + // - Pull out the event to redact from the database. They should have this because the redaction is validated. + // - Redact the event and set the corresponding `unsigned` fields to indicate it as redacted. + // - Replace the event in the database. OutputTypeRedactedEvent OutputType = "redacted_event" ) diff --git a/roomserver/internal/query.go b/roomserver/internal/query.go index ca4af0b25..bede6c881 100644 --- a/roomserver/internal/query.go +++ b/roomserver/internal/query.go @@ -20,6 +20,7 @@ import ( "context" "fmt" + "github.com/matrix-org/dendrite/internal/eventutil" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/roomserver/auth" "github.com/matrix-org/dendrite/roomserver/state" @@ -867,7 +868,7 @@ func getAuthChain( func persistEvents(ctx context.Context, db storage.Database, events []gomatrixserverlib.HeaderedEvent) (types.RoomNID, map[string]types.Event) { var roomNID types.RoomNID backfilledEventMap := make(map[string]types.Event) - for _, ev := range events { + for j, ev := range events { nidMap, err := db.EventNIDs(ctx, ev.AuthEventIDs()) if err != nil { // this shouldn't happen as RequestBackfill already found them logrus.WithError(err).WithField("auth_events", ev.AuthEventIDs()).Error("Failed to find one or more auth events") @@ -891,12 +892,14 @@ func persistEvents(ctx context.Context, db storage.Database, events []gomatrixse // It's also possible for this event to be a redaction which results in another event being // redacted, which we don't care about since we aren't returning it in this backfill. if redactedEventID == ev.EventID() { - ev = ev.Redact().Headered(ev.RoomVersion) - err = ev.SetUnsignedField("redacted_because", redactionEvent) + eventToRedact := ev.Unwrap() + redactedEvent, err := eventutil.RedactEvent(redactionEvent, &eventToRedact) if err != nil { - logrus.WithError(err).WithField("event_id", ev.EventID()).Error("Failed to set unsigned field") + logrus.WithError(err).WithField("event_id", ev.EventID()).Error("Failed to redact event") continue } + ev = redactedEvent.Headered(ev.RoomVersion) + events[j] = ev } backfilledEventMap[ev.EventID()] = types.Event{ EventNID: stateAtEvent.StateEntry.EventNID, diff --git a/roomserver/storage/shared/storage.go b/roomserver/storage/shared/storage.go index a9cb57821..e2e5daf95 100644 --- a/roomserver/storage/shared/storage.go +++ b/roomserver/storage/shared/storage.go @@ -563,6 +563,10 @@ func (d *Database) handleRedactions( // we've seen this redaction before or there is nothing to redact return nil, "", nil } + if redactedEvent.RoomID() != redactionEvent.RoomID() { + // redactions across rooms aren't allowed + return nil, "", nil + } // mark the event as redacted err = redactedEvent.SetUnsignedField("redacted_because", redactionEvent) diff --git a/syncapi/consumers/roomserver.go b/syncapi/consumers/roomserver.go index af7f612b3..c65027168 100644 --- a/syncapi/consumers/roomserver.go +++ b/syncapi/consumers/roomserver.go @@ -81,11 +81,23 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error { switch output.Type { case api.OutputTypeNewRoomEvent: + // Ignore redaction events. We will add them to the database when they are + // validated (when we receive OutputTypeRedactedEvent) + event := output.NewRoomEvent.Event + if event.Type() == gomatrixserverlib.MRoomRedaction && event.StateKey() == nil { + // in the special case where the event redacts itself, just pass the message through because + // we will never see the other part of the pair + if event.Redacts() != event.EventID() { + return nil + } + } return s.onNewRoomEvent(context.TODO(), *output.NewRoomEvent) case api.OutputTypeNewInviteEvent: return s.onNewInviteEvent(context.TODO(), *output.NewInviteEvent) case api.OutputTypeRetireInviteEvent: return s.onRetireInviteEvent(context.TODO(), *output.RetireInviteEvent) + case api.OutputTypeRedactedEvent: + return s.onRedactEvent(context.TODO(), *output.RedactedEvent) default: log.WithField("type", output.Type).Debug( "roomserver output log: ignoring unknown output type", @@ -94,11 +106,25 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error { } } +func (s *OutputRoomEventConsumer) onRedactEvent( + ctx context.Context, msg api.OutputRedactedEvent, +) error { + err := s.db.RedactEvent(ctx, msg.RedactedEventID, &msg.RedactedBecause) + if err != nil { + log.WithError(err).Error("RedactEvent error'd") + return err + } + // fake a room event so we notify clients about the redaction, as if it were + // a normal event. + return s.onNewRoomEvent(ctx, api.OutputNewRoomEvent{ + Event: msg.RedactedBecause, + }) +} + func (s *OutputRoomEventConsumer) onNewRoomEvent( ctx context.Context, msg api.OutputNewRoomEvent, ) error { ev := msg.Event - addsStateEvents := msg.AddsState() ev, err := s.updateStateEvent(ev) @@ -173,12 +199,10 @@ func (s *OutputRoomEventConsumer) onRetireInviteEvent( } func (s *OutputRoomEventConsumer) updateStateEvent(event gomatrixserverlib.HeaderedEvent) (gomatrixserverlib.HeaderedEvent, error) { - var stateKey string if event.StateKey() == nil { - stateKey = "" - } else { - stateKey = *event.StateKey() + return event, nil } + stateKey := *event.StateKey() prevEvent, err := s.db.GetStateEvent( context.TODO(), event.RoomID(), event.Type(), stateKey, diff --git a/syncapi/storage/interface.go b/syncapi/storage/interface.go index c4dae4d09..a5e13b674 100644 --- a/syncapi/storage/interface.go +++ b/syncapi/storage/interface.go @@ -136,4 +136,6 @@ type Database interface { // Returns the filterID as a string. Otherwise returns an error if something // goes wrong. PutFilter(ctx context.Context, localpart string, filter *gomatrixserverlib.Filter) (string, error) + // RedactEvent wipes an event in the database and sets the unsigned.redacted_because key to the redaction event + RedactEvent(ctx context.Context, redactedEventID string, redactedBecause *gomatrixserverlib.HeaderedEvent) error } diff --git a/syncapi/storage/postgres/output_room_events_table.go b/syncapi/storage/postgres/output_room_events_table.go index c7c4dc63b..5315de243 100644 --- a/syncapi/storage/postgres/output_room_events_table.go +++ b/syncapi/storage/postgres/output_room_events_table.go @@ -99,6 +99,9 @@ const selectEarlyEventsSQL = "" + const selectMaxEventIDSQL = "" + "SELECT MAX(id) FROM syncapi_output_room_events" +const updateEventJSONSQL = "" + + "UPDATE syncapi_output_room_events SET headered_event_json=$1 WHERE event_id=$2" + // In order for us to apply the state updates correctly, rows need to be ordered in the order they were received (id). const selectStateInRangeSQL = "" + "SELECT id, headered_event_json, exclude_from_sync, add_state_ids, remove_state_ids" + @@ -120,6 +123,7 @@ type outputRoomEventsStatements struct { selectRecentEventsForSyncStmt *sql.Stmt selectEarlyEventsStmt *sql.Stmt selectStateInRangeStmt *sql.Stmt + updateEventJSONStmt *sql.Stmt } func NewPostgresEventsTable(db *sql.DB) (tables.Events, error) { @@ -149,9 +153,21 @@ func NewPostgresEventsTable(db *sql.DB) (tables.Events, error) { if s.selectStateInRangeStmt, err = db.Prepare(selectStateInRangeSQL); err != nil { return nil, err } + if s.updateEventJSONStmt, err = db.Prepare(updateEventJSONSQL); err != nil { + return nil, err + } return s, nil } +func (s *outputRoomEventsStatements) UpdateEventJSON(ctx context.Context, event *gomatrixserverlib.HeaderedEvent) error { + headeredJSON, err := json.Marshal(event) + if err != nil { + return err + } + _, err = s.updateEventJSONStmt.ExecContext(ctx, headeredJSON, event.EventID()) + return err +} + // selectStateInRange returns the state events between the two given PDU stream positions, exclusive of oldPos, inclusive of newPos. // Results are bucketed based on the room ID. If the same state is overwritten multiple times between the // two positions, only the most recent state is returned. diff --git a/syncapi/storage/shared/syncserver.go b/syncapi/storage/shared/syncserver.go index 01362ddd6..38b503cd0 100644 --- a/syncapi/storage/shared/syncserver.go +++ b/syncapi/storage/shared/syncserver.go @@ -24,6 +24,7 @@ import ( userapi "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/dendrite/eduserver/cache" + "github.com/matrix-org/dendrite/internal/eventutil" "github.com/matrix-org/dendrite/internal/sqlutil" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/syncapi/storage/tables" @@ -597,6 +598,26 @@ func (d *Database) IncrementalSync( return res, nil } +func (d *Database) RedactEvent(ctx context.Context, redactedEventID string, redactedBecause *gomatrixserverlib.HeaderedEvent) error { + redactedEvents, err := d.Events(ctx, []string{redactedEventID}) + if err != nil { + return err + } + if len(redactedEvents) == 0 { + logrus.WithField("event_id", redactedEventID).WithField("redaction_event", redactedBecause.EventID()).Warnf("missing redacted event for redaction") + return nil + } + eventToRedact := redactedEvents[0].Unwrap() + redactionEvent := redactedBecause.Unwrap() + ev, err := eventutil.RedactEvent(&redactionEvent, &eventToRedact) + if err != nil { + return err + } + + newEvent := ev.Headered(redactedBecause.RoomVersion) + return d.OutputEvents.UpdateEventJSON(ctx, &newEvent) +} + // getResponseWithPDUsForCompleteSync creates a response and adds all PDUs needed // to it. It returns toPos and joinedRoomIDs for use of adding EDUs. // nolint:nakedret diff --git a/syncapi/storage/sqlite3/output_room_events_table.go b/syncapi/storage/sqlite3/output_room_events_table.go index 0c909cc4d..da2ea3f69 100644 --- a/syncapi/storage/sqlite3/output_room_events_table.go +++ b/syncapi/storage/sqlite3/output_room_events_table.go @@ -76,6 +76,9 @@ const selectEarlyEventsSQL = "" + const selectMaxEventIDSQL = "" + "SELECT MAX(id) FROM syncapi_output_room_events" +const updateEventJSONSQL = "" + + "UPDATE syncapi_output_room_events SET headered_event_json=$1 WHERE event_id=$2" + // In order for us to apply the state updates correctly, rows need to be ordered in the order they were received (id). /* $1 = oldPos, @@ -109,6 +112,7 @@ type outputRoomEventsStatements struct { selectRecentEventsForSyncStmt *sql.Stmt selectEarlyEventsStmt *sql.Stmt selectStateInRangeStmt *sql.Stmt + updateEventJSONStmt *sql.Stmt } func NewSqliteEventsTable(db *sql.DB, streamID *streamIDStatements) (tables.Events, error) { @@ -140,9 +144,21 @@ func NewSqliteEventsTable(db *sql.DB, streamID *streamIDStatements) (tables.Even if s.selectStateInRangeStmt, err = db.Prepare(selectStateInRangeSQL); err != nil { return nil, err } + if s.updateEventJSONStmt, err = db.Prepare(updateEventJSONSQL); err != nil { + return nil, err + } return s, nil } +func (s *outputRoomEventsStatements) UpdateEventJSON(ctx context.Context, event *gomatrixserverlib.HeaderedEvent) error { + headeredJSON, err := json.Marshal(event) + if err != nil { + return err + } + _, err = s.updateEventJSONStmt.ExecContext(ctx, headeredJSON, event.EventID()) + return err +} + // selectStateInRange returns the state events between the two given PDU stream positions, exclusive of oldPos, inclusive of newPos. // Results are bucketed based on the room ID. If the same state is overwritten multiple times between the // two positions, only the most recent state is returned. diff --git a/syncapi/storage/tables/interface.go b/syncapi/storage/tables/interface.go index 4ac0be4ec..9d239d233 100644 --- a/syncapi/storage/tables/interface.go +++ b/syncapi/storage/tables/interface.go @@ -49,6 +49,7 @@ type Events interface { // SelectEarlyEvents returns the earliest events in the given room. SelectEarlyEvents(ctx context.Context, txn *sql.Tx, roomID string, r types.Range, limit int) ([]types.StreamEvent, error) SelectEvents(ctx context.Context, txn *sql.Tx, eventIDs []string) ([]types.StreamEvent, error) + UpdateEventJSON(ctx context.Context, event *gomatrixserverlib.HeaderedEvent) error } // Topology keeps track of the depths and stream positions for all events. diff --git a/sytest-whitelist b/sytest-whitelist index 30380af0e..0628ea26e 100644 --- a/sytest-whitelist +++ b/sytest-whitelist @@ -160,14 +160,6 @@ User can create and send/receive messages in a room with version 1 POST /createRoom ignores attempts to set the room version via creation_content Inbound federation rejects remote attempts to join local users to rooms Inbound federation rejects remote attempts to kick local users to rooms -# SyTest currently only implements the v1 endpoints for /send_join and /send_leave, -# whereas Dendrite only supports the v2 endpoints for those, so let's ignore this -# test for now. -#An event which redacts itself should be ignored -# SyTest currently only implements the v1 endpoints for /send_join and /send_leave, -# whereas Dendrite only supports the v2 endpoints for those, so let's ignore this -# test for now. -#A pair of events which redact each other should be ignored Full state sync includes joined rooms A message sent after an initial sync appears in the timeline of an incremental sync. Can add tag @@ -295,6 +287,14 @@ POST /rooms/:room_id/redact/:event_id as random user does not redact message POST /redact disallows redaction of event in different room An event which redacts itself should be ignored A pair of events which redact each other should be ignored +Redaction of a redaction redacts the redaction reason +An event which redacts an event in a different room should be ignored +Can receive redactions from regular users over federation in room version 1 +Can receive redactions from regular users over federation in room version 2 +Can receive redactions from regular users over federation in room version 3 +Can receive redactions from regular users over federation in room version 4 +Can receive redactions from regular users over federation in room version 5 +Can receive redactions from regular users over federation in room version 6 Outbound federation can backfill events Inbound federation can backfill events Backfill checks the events requested belong to the room From 99b50f30a092c1a9fa213aff84eef80f087d0be6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 9 Jul 2020 15:39:35 +0100 Subject: [PATCH 09/15] Reduce federation sender wedges (#1191) * Reduce federation sender wedges * Un-goroutine the goroutines --- federationsender/queue/destinationqueue.go | 92 ++++++++++------------ 1 file changed, 41 insertions(+), 51 deletions(-) diff --git a/federationsender/queue/destinationqueue.go b/federationsender/queue/destinationqueue.go index 7751326c3..82cb343f3 100644 --- a/federationsender/queue/destinationqueue.go +++ b/federationsender/queue/destinationqueue.go @@ -32,6 +32,7 @@ import ( ) const maxPDUsPerTransaction = 50 +const queueIdleTimeout = time.Second * 30 // destinationQueue is a queue of events for a single destination. // It is responsible for sending the events to the destination and @@ -52,7 +53,6 @@ type destinationQueue struct { transactionIDMutex sync.Mutex // protects transactionID transactionID gomatrixserverlib.TransactionID // last transaction ID transactionCount atomic.Int32 // how many events in this transaction so far - pendingPDUs atomic.Int64 // how many PDUs are waiting to be sent pendingEDUs []*gomatrixserverlib.EDU // owned by backgroundSend pendingInvites []*gomatrixserverlib.InviteV2Request // owned by backgroundSend notifyPDUs chan bool // interrupts idle wait for PDUs @@ -68,7 +68,6 @@ func (oq *destinationQueue) sendEvent(nid int64) { log.Infof("%s is blacklisted; dropping event", oq.destination) return } - oq.wakeQueueIfNeeded() // Create a transaction ID. We'll either do this if we don't have // one made up yet, or if we've exceeded the number of maximum // events allowed in a single tranaction. We'll reset the counter @@ -95,11 +94,13 @@ func (oq *destinationQueue) sendEvent(nid int64) { // We've successfully added a PDU to the transaction so increase // the counter. oq.transactionCount.Add(1) - // Signal that we've sent a new PDU. This will cause the queue to - // wake up if it's asleep. The return to the Add function will only - // be 1 if the previous value was 0, e.g. nothing was waiting before. - if oq.pendingPDUs.Add(1) == 1 { - oq.notifyPDUs <- true + // Wake up the queue if it's asleep. + oq.wakeQueueIfNeeded() + // If we're blocking on waiting PDUs then tell the queue that we + // have work to do. + select { + case oq.notifyPDUs <- true: + default: } } @@ -138,26 +139,33 @@ func (oq *destinationQueue) wakeQueueIfNeeded() { } // If we aren't running then wake up the queue. if !oq.running.Load() { - // Look up how many events are pending in this queue. We need - // to do this so that the queue thinks it has work to do. - count, err := oq.db.GetPendingPDUCount( - context.TODO(), - oq.destination, - ) - if err == nil { - oq.pendingPDUs.Store(count) - log.Printf("Destination queue %q has %d pending PDUs", oq.destination, count) - } else { - log.WithError(err).Errorf("Can't get pending PDU count for %q destination queue", oq.destination) - } - if count > 0 { - oq.notifyPDUs <- true - } - // Then start the queue. + // Start the queue. go oq.backgroundSend() } } +// waitForPDUs returns a channel for pending PDUs, which will be +// used in backgroundSend select. It returns a closed channel if +// there is something pending right now, or an open channel if +// we're waiting for something. +func (oq *destinationQueue) waitForPDUs() chan bool { + pendingPDUs, err := oq.db.GetPendingPDUCount(context.TODO(), oq.destination) + if err != nil { + log.WithError(err).Errorf("Failed to get pending PDU count on queue %q", oq.destination) + } + // If there are PDUs pending right now then we'll return a closed + // channel. This will mean that the backgroundSend will not block. + if pendingPDUs > 0 { + ch := make(chan bool, 1) + close(ch) + return ch + } + // If there are no PDUs pending right now then instead we'll return + // the notify channel, so that backgroundSend can pick up normal + // notifications from sendEvent. + return oq.notifyPDUs +} + // backgroundSend is the worker goroutine for sending events. // nolint:gocyclo func (oq *destinationQueue) backgroundSend() { @@ -169,12 +177,15 @@ func (oq *destinationQueue) backgroundSend() { defer oq.running.Store(false) for { + pendingPDUs := false + // If we have nothing to do then wait either for incoming events, or // until we hit an idle timeout. select { - case <-oq.notifyPDUs: + case <-oq.waitForPDUs(): // We were woken up because there are new PDUs waiting in the // database. + pendingPDUs = true case edu := <-oq.incomingEDUs: // EDUs are handled in-memory for now. We will try to keep // the ordering intact. @@ -204,10 +215,11 @@ func (oq *destinationQueue) backgroundSend() { for len(oq.incomingInvites) > 0 { oq.pendingInvites = append(oq.pendingInvites, <-oq.incomingInvites) } - case <-time.After(time.Second * 30): + case <-time.After(queueIdleTimeout): // The worker is idle so stop the goroutine. It'll get // restarted automatically the next time we have an event to // send. + log.Infof("Queue %q has been idle for %s, going to sleep", oq.destination, queueIdleTimeout) return } @@ -220,12 +232,13 @@ func (oq *destinationQueue) backgroundSend() { select { case <-time.After(duration): case <-oq.interruptBackoff: + log.Infof("Interrupting backoff for %q", oq.destination) } oq.backingOff.Store(false) } // If we have pending PDUs or EDUs then construct a transaction. - if oq.pendingPDUs.Load() > 0 || len(oq.pendingEDUs) > 0 { + if pendingPDUs || len(oq.pendingEDUs) > 0 { // Try sending the next transaction and see what happens. transaction, terr := oq.nextTransaction(oq.pendingEDUs) if terr != nil { @@ -236,6 +249,7 @@ func (oq *destinationQueue) backgroundSend() { // buffers at this point. The PDU clean-up is already on a defer. oq.cleanPendingEDUs() oq.cleanPendingInvites() + log.Infof("Blacklisting %q due to errors", oq.destination) return } else { // We haven't been told to give up terminally yet but we still have @@ -262,6 +276,7 @@ func (oq *destinationQueue) backgroundSend() { if giveUp := oq.statistics.Failure(); giveUp { // It's been suggested that we should give up because // the backoff has exceeded a maximum allowable value. + log.Infof("Blacklisting %q due to errors", oq.destination) return } } else if sent > 0 { @@ -273,17 +288,6 @@ func (oq *destinationQueue) backgroundSend() { oq.cleanPendingInvites() } } - - // If something else has come along while we were busy sending - // the previous transaction then we don't want the next loop - // iteration to sleep. Send a message if someone else hasn't - // already sent a wake-up. - if oq.pendingPDUs.Load() > 0 { - select { - case oq.notifyPDUs <- true: - default: - } - } } } @@ -349,17 +353,6 @@ func (oq *destinationQueue) nextTransaction( // If we didn't get anything from the database and there are no // pending EDUs then there's nothing to do - stop here. if len(pdus) == 0 && len(pendingEDUs) == 0 { - log.Warnf("Expected PDUs/EDUs for destination %q but got none", oq.destination) - // This shouldn't really happen but since it has, let's check - // how many events are *really* in the database that are waiting. - if count, cerr := oq.db.GetPendingPDUCount( - context.TODO(), - oq.destination, - ); cerr == nil { - oq.pendingPDUs.Store(count) - } else { - log.Warnf("Failed to retrieve pending PDU count for %q", oq.destination) - } return false, nil } @@ -396,9 +389,6 @@ func (oq *destinationQueue) nextTransaction( _, err = oq.client.SendTransaction(ctx, t) switch err.(type) { case nil: - // No error was returned so the transaction looks to have - // been successfully sent. - oq.pendingPDUs.Sub(int64(len(t.PDUs))) // Clean up the transaction in the database. if err = oq.db.CleanTransactionPDUs( context.Background(), From 9cc52f47f3ea2e8a009731cc46117cb996aed722 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 9 Jul 2020 17:48:56 +0100 Subject: [PATCH 10/15] Use TransactionWriter to reduce database lock issues on SQLite (#1192) --- federationsender/storage/sqlite3/storage.go | 43 ++++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/federationsender/storage/sqlite3/storage.go b/federationsender/storage/sqlite3/storage.go index 7ba51fb52..7fe6b65b1 100644 --- a/federationsender/storage/sqlite3/storage.go +++ b/federationsender/storage/sqlite3/storage.go @@ -35,7 +35,9 @@ type Database struct { queuePDUsStatements queueJSONStatements sqlutil.PartitionOffsetStatements - db *sql.DB + db *sql.DB + queuePDUsWriter *sqlutil.TransactionWriter + queueJSONWriter *sqlutil.TransactionWriter } // NewDatabase opens a new database @@ -74,6 +76,9 @@ func (d *Database) prepare() error { return err } + d.queuePDUsWriter = sqlutil.NewTransactionWriter() + d.queueJSONWriter = sqlutil.NewTransactionWriter() + return d.PartitionOffsetStatements.Prepare(d.db, "federationsender") } @@ -145,12 +150,16 @@ func (d *Database) GetJoinedHosts( // metadata entries. func (d *Database) StoreJSON( ctx context.Context, js string, -) (int64, error) { - nid, err := d.insertQueueJSON(ctx, nil, js) - if err != nil { - return 0, fmt.Errorf("d.insertQueueJSON: %w", err) - } - return nid, nil +) (nid int64, err error) { + err = d.queueJSONWriter.Do(d.db, func(txn *sql.Tx) error { + n, e := d.insertQueueJSON(ctx, nil, js) + if e != nil { + return fmt.Errorf("d.insertQueueJSON: %w", e) + } + nid = n + return nil + }) + return } // AssociatePDUWithDestination creates an association that the @@ -162,7 +171,7 @@ func (d *Database) AssociatePDUWithDestination( serverName gomatrixserverlib.ServerName, nids []int64, ) error { - return sqlutil.WithTransaction(d.db, func(txn *sql.Tx) error { + return d.queuePDUsWriter.Do(d.db, func(txn *sql.Tx) error { for _, nid := range nids { if err := d.insertQueuePDU( ctx, // context @@ -230,18 +239,18 @@ func (d *Database) CleanTransactionPDUs( serverName gomatrixserverlib.ServerName, transactionID gomatrixserverlib.TransactionID, ) error { - return sqlutil.WithTransaction(d.db, func(txn *sql.Tx) error { - nids, err := d.selectQueuePDUs(ctx, txn, serverName, transactionID, 50) + var err error + var nids []int64 + var deleteNIDs []int64 + if err = d.queuePDUsWriter.Do(d.db, func(txn *sql.Tx) error { + nids, err = d.selectQueuePDUs(ctx, txn, serverName, transactionID, 50) if err != nil { return fmt.Errorf("d.selectQueuePDUs: %w", err) } - if err = d.deleteQueueTransaction(ctx, txn, serverName, transactionID); err != nil { return fmt.Errorf("d.deleteQueueTransaction: %w", err) } - var count int64 - var deleteNIDs []int64 for _, nid := range nids { count, err = d.selectQueueReferenceJSONCount(ctx, txn, nid) if err != nil { @@ -251,15 +260,19 @@ func (d *Database) CleanTransactionPDUs( deleteNIDs = append(deleteNIDs, nid) } } - + return nil + }); err != nil { + return err + } + err = d.queueJSONWriter.Do(d.db, func(txn *sql.Tx) error { if len(deleteNIDs) > 0 { if err = d.deleteQueueJSON(ctx, txn, deleteNIDs); err != nil { return fmt.Errorf("d.deleteQueueJSON: %w", err) } } - return nil }) + return err } // GetPendingPDUCount returns the number of PDUs waiting to be From abf26c12f1a97fd2894a0509de9cf4a91c79d3ab Mon Sep 17 00:00:00 2001 From: Kegsay Date: Fri, 10 Jul 2020 00:39:44 +0100 Subject: [PATCH 11/15] Add User-Interactive Authentication (#1193) * Add User-Interactive Authentication And use it when deleting a device. With tests. * Make remaining sytest pass * Linting * 403 not 401 on wrong user/pass --- clientapi/auth/password.go | 75 +++++++ clientapi/auth/user_interactive.go | 248 ++++++++++++++++++++++++ clientapi/auth/user_interactive_test.go | 174 +++++++++++++++++ clientapi/routing/device.go | 38 +++- clientapi/routing/login.go | 176 ++++++----------- clientapi/routing/routing.go | 4 +- sytest-whitelist | 6 + 7 files changed, 594 insertions(+), 127 deletions(-) create mode 100644 clientapi/auth/password.go create mode 100644 clientapi/auth/user_interactive.go create mode 100644 clientapi/auth/user_interactive_test.go diff --git a/clientapi/auth/password.go b/clientapi/auth/password.go new file mode 100644 index 000000000..f48149252 --- /dev/null +++ b/clientapi/auth/password.go @@ -0,0 +1,75 @@ +// 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 auth + +import ( + "context" + "net/http" + + "github.com/matrix-org/dendrite/clientapi/jsonerror" + "github.com/matrix-org/dendrite/clientapi/userutil" + "github.com/matrix-org/dendrite/internal/config" + "github.com/matrix-org/dendrite/userapi/api" + "github.com/matrix-org/util" +) + +type GetAccountByPassword func(ctx context.Context, localpart, password string) (*api.Account, error) + +type PasswordRequest struct { + Login + Password string `json:"password"` +} + +// LoginTypePassword implements https://matrix.org/docs/spec/client_server/r0.6.1#password-based +type LoginTypePassword struct { + GetAccountByPassword GetAccountByPassword + Config *config.Dendrite +} + +func (t *LoginTypePassword) Name() string { + return "m.login.password" +} + +func (t *LoginTypePassword) Request() interface{} { + return &PasswordRequest{} +} + +func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login, *util.JSONResponse) { + r := req.(*PasswordRequest) + username := r.Username() + if username == "" { + return nil, &util.JSONResponse{ + Code: http.StatusUnauthorized, + JSON: jsonerror.BadJSON("'user' must be supplied."), + } + } + localpart, err := userutil.ParseUsernameParam(username, &t.Config.Matrix.ServerName) + if err != nil { + return nil, &util.JSONResponse{ + Code: http.StatusUnauthorized, + JSON: jsonerror.InvalidUsername(err.Error()), + } + } + _, err = t.GetAccountByPassword(ctx, localpart, r.Password) + if err != nil { + // Technically we could tell them if the user does not exist by checking if err == sql.ErrNoRows + // but that would leak the existence of the user. + return nil, &util.JSONResponse{ + Code: http.StatusForbidden, + JSON: jsonerror.Forbidden("username or password was incorrect, or the account does not exist"), + } + } + return &r.Login, nil +} diff --git a/clientapi/auth/user_interactive.go b/clientapi/auth/user_interactive.go new file mode 100644 index 000000000..581a85f0b --- /dev/null +++ b/clientapi/auth/user_interactive.go @@ -0,0 +1,248 @@ +// 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 auth + +import ( + "context" + "encoding/json" + "net/http" + + "github.com/matrix-org/dendrite/clientapi/jsonerror" + "github.com/matrix-org/dendrite/internal/config" + "github.com/matrix-org/dendrite/userapi/api" + "github.com/matrix-org/util" + "github.com/sirupsen/logrus" + "github.com/tidwall/gjson" +) + +// Type represents an auth type +// https://matrix.org/docs/spec/client_server/r0.6.1#authentication-types +type Type interface { + // Name returns the name of the auth type e.g `m.login.password` + Name() string + // Request returns a pointer to a new request body struct to unmarshal into. + Request() interface{} + // Login with the auth type, returning an error response on failure. + // Not all types support login, only m.login.password and m.login.token + // See https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-login + // `req` is guaranteed to be the type returned from Request() + // This function will be called when doing login and when doing 'sudo' style + // actions e.g deleting devices. The response must be a 401 as per: + // "If the homeserver decides that an attempt on a stage was unsuccessful, but the + // client may make a second attempt, it returns the same HTTP status 401 response as above, + // with the addition of the standard errcode and error fields describing the error." + Login(ctx context.Context, req interface{}) (login *Login, errRes *util.JSONResponse) + // TODO: Extend to support Register() flow + // Register(ctx context.Context, sessionID string, req interface{}) +} + +// LoginIdentifier represents identifier types +// https://matrix.org/docs/spec/client_server/r0.6.1#identifier-types +type LoginIdentifier struct { + Type string `json:"type"` + // when type = m.id.user + User string `json:"user"` + // when type = m.id.thirdparty + Medium string `json:"medium"` + Address string `json:"address"` +} + +// Login represents the shared fields used in all forms of login/sudo endpoints. +type Login struct { + Type string `json:"type"` + Identifier LoginIdentifier `json:"identifier"` + User string `json:"user"` // deprecated in favour of identifier + Medium string `json:"medium"` // deprecated in favour of identifier + Address string `json:"address"` // deprecated in favour of identifier + + // Both DeviceID and InitialDisplayName can be omitted, or empty strings ("") + // Thus a pointer is needed to differentiate between the two + InitialDisplayName *string `json:"initial_device_display_name"` + DeviceID *string `json:"device_id"` +} + +// Username returns the user localpart/user_id in this request, if it exists. +func (r *Login) Username() string { + if r.Identifier.Type == "m.id.user" { + return r.Identifier.User + } + // deprecated but without it Riot iOS won't log in + return r.User +} + +// ThirdPartyID returns the 3PID medium and address for this login, if it exists. +func (r *Login) ThirdPartyID() (medium, address string) { + if r.Identifier.Type == "m.id.thirdparty" { + return r.Identifier.Medium, r.Identifier.Address + } + // deprecated + if r.Medium == "email" { + return "email", r.Address + } + return "", "" +} + +type userInteractiveFlow struct { + Stages []string `json:"stages"` +} + +// UserInteractive checks that the user is who they claim to be, via a UI auth. +// This is used for things like device deletion and password reset where +// the user already has a valid access token, but we want to double-check +// that it isn't stolen by re-authenticating them. +type UserInteractive struct { + Flows []userInteractiveFlow + // Map of login type to implementation + Types map[string]Type + // Map of session ID to completed login types, will need to be extended in future + Sessions map[string][]string +} + +func NewUserInteractive(getAccByPass GetAccountByPassword, cfg *config.Dendrite) *UserInteractive { + typePassword := &LoginTypePassword{ + GetAccountByPassword: getAccByPass, + Config: cfg, + } + // TODO: Add SSO login + return &UserInteractive{ + Flows: []userInteractiveFlow{ + { + Stages: []string{typePassword.Name()}, + }, + }, + Types: map[string]Type{ + typePassword.Name(): typePassword, + }, + Sessions: make(map[string][]string), + } +} + +func (u *UserInteractive) IsSingleStageFlow(authType string) bool { + for _, f := range u.Flows { + if len(f.Stages) == 1 && f.Stages[0] == authType { + return true + } + } + return false +} + +func (u *UserInteractive) AddCompletedStage(sessionID, authType string) { + // TODO: Handle multi-stage flows + delete(u.Sessions, sessionID) +} + +// Challenge returns an HTTP 401 with the supported flows for authenticating +func (u *UserInteractive) Challenge(sessionID string) *util.JSONResponse { + return &util.JSONResponse{ + Code: 401, + JSON: struct { + Flows []userInteractiveFlow `json:"flows"` + Session string `json:"session"` + // TODO: Return any additional `params` + Params map[string]interface{} `json:"params"` + }{ + u.Flows, + sessionID, + make(map[string]interface{}), + }, + } +} + +// NewSession returns a challenge with a new session ID and remembers the session ID +func (u *UserInteractive) NewSession() *util.JSONResponse { + sessionID, err := GenerateAccessToken() + if err != nil { + logrus.WithError(err).Error("failed to generate session ID") + res := jsonerror.InternalServerError() + return &res + } + u.Sessions[sessionID] = []string{} + return u.Challenge(sessionID) +} + +// ResponseWithChallenge mixes together a JSON body (e.g an error with errcode/message) with the +// standard challenge response. +func (u *UserInteractive) ResponseWithChallenge(sessionID string, response interface{}) *util.JSONResponse { + mixedObjects := make(map[string]interface{}) + b, err := json.Marshal(response) + if err != nil { + ise := jsonerror.InternalServerError() + return &ise + } + _ = json.Unmarshal(b, &mixedObjects) + challenge := u.Challenge(sessionID) + b, err = json.Marshal(challenge.JSON) + if err != nil { + ise := jsonerror.InternalServerError() + return &ise + } + _ = json.Unmarshal(b, &mixedObjects) + + return &util.JSONResponse{ + Code: 401, + JSON: mixedObjects, + } +} + +// Verify returns an error/challenge response to send to the client, or nil if the user is authenticated. +// `bodyBytes` is the HTTP request body which must contain an `auth` key. +// Returns the login that was verified for additional checks if required. +func (u *UserInteractive) Verify(ctx context.Context, bodyBytes []byte, device *api.Device) (*Login, *util.JSONResponse) { + // TODO: rate limit + + // "A client should first make a request with no auth parameter. The homeserver returns an HTTP 401 response, with a JSON body" + // https://matrix.org/docs/spec/client_server/r0.6.1#user-interactive-api-in-the-rest-api + hasResponse := gjson.GetBytes(bodyBytes, "auth").Exists() + if !hasResponse { + return nil, u.NewSession() + } + + // extract the type so we know which login type to use + authType := gjson.GetBytes(bodyBytes, "auth.type").Str + loginType, ok := u.Types[authType] + if !ok { + return nil, &util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.BadJSON("unknown auth.type: " + authType), + } + } + + // retrieve the session + sessionID := gjson.GetBytes(bodyBytes, "auth.session").Str + if _, ok = u.Sessions[sessionID]; !ok { + // if the login type is part of a single stage flow then allow them to omit the session ID + if !u.IsSingleStageFlow(authType) { + return nil, &util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.Unknown("missing or unknown auth.session"), + } + } + } + + r := loginType.Request() + if err := json.Unmarshal([]byte(gjson.GetBytes(bodyBytes, "auth").Raw), r); err != nil { + return nil, &util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.BadJSON("The request body could not be decoded into valid JSON. " + err.Error()), + } + } + login, resErr := loginType.Login(ctx, r) + if resErr == nil { + u.AddCompletedStage(sessionID, authType) + // TODO: Check if there's more stages to go and return an error + return login, nil + } + return nil, u.ResponseWithChallenge(sessionID, resErr.JSON) +} diff --git a/clientapi/auth/user_interactive_test.go b/clientapi/auth/user_interactive_test.go new file mode 100644 index 000000000..d12652c08 --- /dev/null +++ b/clientapi/auth/user_interactive_test.go @@ -0,0 +1,174 @@ +package auth + +import ( + "context" + "encoding/json" + "fmt" + "testing" + + "github.com/matrix-org/dendrite/internal/config" + "github.com/matrix-org/dendrite/userapi/api" + "github.com/matrix-org/gomatrixserverlib" + "github.com/matrix-org/util" +) + +var ( + ctx = context.Background() + serverName = gomatrixserverlib.ServerName("example.com") + // space separated localpart+password -> account + lookup = make(map[string]*api.Account) + device = &api.Device{ + AccessToken: "flibble", + DisplayName: "My Device", + ID: "device_id_goes_here", + } +) + +func getAccountByPassword(ctx context.Context, localpart, plaintextPassword string) (*api.Account, error) { + acc, ok := lookup[localpart+" "+plaintextPassword] + if !ok { + return nil, fmt.Errorf("unknown user/password") + } + return acc, nil +} + +func setup() *UserInteractive { + cfg := &config.Dendrite{} + cfg.Matrix.ServerName = serverName + return NewUserInteractive(getAccountByPassword, cfg) +} + +func TestUserInteractiveChallenge(t *testing.T) { + uia := setup() + // no auth key results in a challenge + _, errRes := uia.Verify(ctx, []byte(`{}`), device) + if errRes == nil { + t.Fatalf("Verify succeeded with {} but expected failure") + } + if errRes.Code != 401 { + t.Errorf("Expected HTTP 401, got %d", errRes.Code) + } +} + +func TestUserInteractivePasswordLogin(t *testing.T) { + uia := setup() + // valid password login succeeds when an account exists + lookup["alice herpassword"] = &api.Account{ + Localpart: "alice", + ServerName: serverName, + UserID: fmt.Sprintf("@alice:%s", serverName), + } + // valid password requests + testCases := []json.RawMessage{ + // deprecated form + []byte(`{ + "auth": { + "type": "m.login.password", + "user": "alice", + "password": "herpassword" + } + }`), + // new form + []byte(`{ + "auth": { + "type": "m.login.password", + "identifier": { + "type": "m.id.user", + "user": "alice" + }, + "password": "herpassword" + } + }`), + } + for _, tc := range testCases { + _, errRes := uia.Verify(ctx, tc, device) + if errRes != nil { + t.Errorf("Verify failed but expected success for request: %s - got %+v", string(tc), errRes) + } + } +} + +func TestUserInteractivePasswordBadLogin(t *testing.T) { + uia := setup() + // password login fails when an account exists but is specced wrong + lookup["bob hispassword"] = &api.Account{ + Localpart: "bob", + ServerName: serverName, + UserID: fmt.Sprintf("@bob:%s", serverName), + } + // invalid password requests + testCases := []struct { + body json.RawMessage + wantRes util.JSONResponse + }{ + { + // fields not in an auth dict + body: []byte(`{ + "type": "m.login.password", + "user": "bob", + "password": "hispassword" + }`), + wantRes: util.JSONResponse{ + Code: 401, + }, + }, + { + // wrong type + body: []byte(`{ + "auth": { + "type": "m.login.not_password", + "identifier": { + "type": "m.id.user", + "user": "bob" + }, + "password": "hispassword" + } + }`), + wantRes: util.JSONResponse{ + Code: 400, + }, + }, + { + // identifier type is wrong + body: []byte(`{ + "auth": { + "type": "m.login.password", + "identifier": { + "type": "m.id.thirdparty", + "user": "bob" + }, + "password": "hispassword" + } + }`), + wantRes: util.JSONResponse{ + Code: 401, + }, + }, + { + // wrong password + body: []byte(`{ + "auth": { + "type": "m.login.password", + "identifier": { + "type": "m.id.user", + "user": "bob" + }, + "password": "not_his_password" + } + }`), + wantRes: util.JSONResponse{ + Code: 401, + }, + }, + } + for _, tc := range testCases { + _, errRes := uia.Verify(ctx, tc.body, device) + if errRes == nil { + t.Errorf("Verify succeeded but expected failure for request: %s", string(tc.body)) + continue + } + if errRes.Code != tc.wantRes.Code { + t.Errorf("got code %d want code %d for request: %s", errRes.Code, tc.wantRes.Code, string(tc.body)) + } + } +} diff --git a/clientapi/routing/device.go b/clientapi/routing/device.go index 51a15a882..01310400a 100644 --- a/clientapi/routing/device.go +++ b/clientapi/routing/device.go @@ -17,8 +17,10 @@ package routing import ( "database/sql" "encoding/json" + "io/ioutil" "net/http" + "github.com/matrix-org/dendrite/clientapi/auth" "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/dendrite/userapi/storage/devices" @@ -72,7 +74,8 @@ func GetDeviceByID( return util.JSONResponse{ Code: http.StatusOK, JSON: deviceJSON{ - DeviceID: dev.ID, + DeviceID: dev.ID, + DisplayName: dev.DisplayName, }, } } @@ -99,7 +102,8 @@ func GetDevicesByLocalpart( for _, dev := range deviceList { res.Devices = append(res.Devices, deviceJSON{ - DeviceID: dev.ID, + DeviceID: dev.ID, + DisplayName: dev.DisplayName, }) } @@ -161,20 +165,40 @@ func UpdateDeviceByID( // DeleteDeviceById handles DELETE requests to /devices/{deviceId} func DeleteDeviceById( - req *http.Request, deviceDB devices.Database, device *api.Device, + req *http.Request, userInteractiveAuth *auth.UserInteractive, deviceDB devices.Database, device *api.Device, deviceID string, ) util.JSONResponse { + ctx := req.Context() + defer req.Body.Close() // nolint:errcheck + bodyBytes, err := ioutil.ReadAll(req.Body) + if err != nil { + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.BadJSON("The request body could not be read: " + err.Error()), + } + } + login, errRes := userInteractiveAuth.Verify(ctx, bodyBytes, device) + if errRes != nil { + return *errRes + } + localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID) if err != nil { - util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed") + util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.SplitID failed") return jsonerror.InternalServerError() } - ctx := req.Context() - defer req.Body.Close() // nolint: errcheck + // make sure that the access token being used matches the login creds used for user interactive auth, else + // 1 compromised access token could be used to logout all devices. + if login.Username() != localpart && login.Username() != device.UserID { + return util.JSONResponse{ + Code: 403, + JSON: jsonerror.Forbidden("Cannot delete another user's device"), + } + } if err := deviceDB.RemoveDevice(ctx, deviceID, localpart); err != nil { - util.GetLogger(req.Context()).WithError(err).Error("deviceDB.RemoveDevice failed") + util.GetLogger(ctx).WithError(err).Error("deviceDB.RemoveDevice failed") return jsonerror.InternalServerError() } diff --git a/clientapi/routing/login.go b/clientapi/routing/login.go index dc0180da6..7f47aafff 100644 --- a/clientapi/routing/login.go +++ b/clientapi/routing/login.go @@ -15,46 +15,20 @@ package routing import ( - "net/http" - "context" + "net/http" "github.com/matrix-org/dendrite/clientapi/auth" "github.com/matrix-org/dendrite/clientapi/httputil" "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/clientapi/userutil" "github.com/matrix-org/dendrite/internal/config" - "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/dendrite/userapi/storage/accounts" "github.com/matrix-org/dendrite/userapi/storage/devices" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" ) -type loginFlows struct { - Flows []flow `json:"flows"` -} - -type flow struct { - Type string `json:"type"` - Stages []string `json:"stages"` -} - -type loginIdentifier struct { - Type string `json:"type"` - User string `json:"user"` -} - -type passwordRequest struct { - Identifier loginIdentifier `json:"identifier"` - User string `json:"user"` // deprecated in favour of identifier - Password string `json:"password"` - // Both DeviceID and InitialDisplayName can be omitted, or empty strings ("") - // Thus a pointer is needed to differentiate between the two - InitialDisplayName *string `json:"initial_device_display_name"` - DeviceID *string `json:"device_id"` -} - type loginResponse struct { UserID string `json:"user_id"` AccessToken string `json:"access_token"` @@ -62,9 +36,21 @@ type loginResponse struct { DeviceID string `json:"device_id"` } -func passwordLogin() loginFlows { - f := loginFlows{} - s := flow{"m.login.password", []string{"m.login.password"}} +type flows struct { + Flows []flow `json:"flows"` +} + +type flow struct { + Type string `json:"type"` + Stages []string `json:"stages"` +} + +func passwordLogin() flows { + f := flows{} + s := flow{ + Type: "m.login.password", + Stages: []string{"m.login.password"}, + } f.Flows = append(f.Flows, s) return f } @@ -74,69 +60,28 @@ func Login( req *http.Request, accountDB accounts.Database, deviceDB devices.Database, cfg *config.Dendrite, ) util.JSONResponse { - if req.Method == http.MethodGet { // TODO: support other forms of login other than password, depending on config options + if req.Method == http.MethodGet { + // TODO: support other forms of login other than password, depending on config options return util.JSONResponse{ Code: http.StatusOK, JSON: passwordLogin(), } } else if req.Method == http.MethodPost { - var r passwordRequest - var acc *api.Account - var errJSON *util.JSONResponse - resErr := httputil.UnmarshalJSONRequest(req, &r) + typePassword := auth.LoginTypePassword{ + GetAccountByPassword: accountDB.GetAccountByPassword, + Config: cfg, + } + r := typePassword.Request() + resErr := httputil.UnmarshalJSONRequest(req, r) if resErr != nil { return *resErr } - switch r.Identifier.Type { - case "m.id.user": - if r.Identifier.User == "" { - return util.JSONResponse{ - Code: http.StatusBadRequest, - JSON: jsonerror.BadJSON("'user' must be supplied."), - } - } - acc, errJSON = r.processUsernamePasswordLoginRequest(req, accountDB, cfg, r.Identifier.User) - if errJSON != nil { - return *errJSON - } - default: - // TODO: The below behaviour is deprecated but without it Riot iOS won't log in - if r.User != "" { - acc, errJSON = r.processUsernamePasswordLoginRequest(req, accountDB, cfg, r.User) - if errJSON != nil { - return *errJSON - } - } else { - return util.JSONResponse{ - Code: http.StatusBadRequest, - JSON: jsonerror.BadJSON("login identifier '" + r.Identifier.Type + "' not supported"), - } - } - } - - token, err := auth.GenerateAccessToken() - if err != nil { - util.GetLogger(req.Context()).WithError(err).Error("auth.GenerateAccessToken failed") - return jsonerror.InternalServerError() - } - - dev, err := getDevice(req.Context(), r, deviceDB, acc, token) - if err != nil { - return util.JSONResponse{ - Code: http.StatusInternalServerError, - JSON: jsonerror.Unknown("failed to create device: " + err.Error()), - } - } - - return util.JSONResponse{ - Code: http.StatusOK, - JSON: loginResponse{ - UserID: dev.UserID, - AccessToken: dev.AccessToken, - HomeServer: cfg.Matrix.ServerName, - DeviceID: dev.ID, - }, + login, authErr := typePassword.Login(req.Context(), r) + if authErr != nil { + return *authErr } + // make a device/access token + return completeAuth(req.Context(), cfg.Matrix.ServerName, deviceDB, login) } return util.JSONResponse{ Code: http.StatusMethodNotAllowed, @@ -144,45 +89,38 @@ func Login( } } -// getDevice returns a new or existing device -func getDevice( - ctx context.Context, - r passwordRequest, - deviceDB devices.Database, - acc *api.Account, - token string, -) (dev *api.Device, err error) { - dev, err = deviceDB.CreateDevice( - ctx, acc.Localpart, r.DeviceID, token, r.InitialDisplayName, +func completeAuth( + ctx context.Context, serverName gomatrixserverlib.ServerName, deviceDB devices.Database, login *auth.Login, +) util.JSONResponse { + token, err := auth.GenerateAccessToken() + if err != nil { + util.GetLogger(ctx).WithError(err).Error("auth.GenerateAccessToken failed") + return jsonerror.InternalServerError() + } + + localpart, err := userutil.ParseUsernameParam(login.Username(), &serverName) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("auth.ParseUsernameParam failed") + return jsonerror.InternalServerError() + } + + dev, err := deviceDB.CreateDevice( + ctx, localpart, login.DeviceID, token, login.InitialDisplayName, ) - return -} - -func (r *passwordRequest) processUsernamePasswordLoginRequest( - req *http.Request, accountDB accounts.Database, - cfg *config.Dendrite, username string, -) (acc *api.Account, errJSON *util.JSONResponse) { - util.GetLogger(req.Context()).WithField("user", username).Info("Processing login request") - - localpart, err := userutil.ParseUsernameParam(username, &cfg.Matrix.ServerName) if err != nil { - errJSON = &util.JSONResponse{ - Code: http.StatusBadRequest, - JSON: jsonerror.InvalidUsername(err.Error()), + return util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: jsonerror.Unknown("failed to create device: " + err.Error()), } - return } - acc, err = accountDB.GetAccountByPassword(req.Context(), localpart, r.Password) - if err != nil { - // Technically we could tell them if the user does not exist by checking if err == sql.ErrNoRows - // but that would leak the existence of the user. - errJSON = &util.JSONResponse{ - Code: http.StatusForbidden, - JSON: jsonerror.Forbidden("username or password was incorrect, or the account does not exist"), - } - return + return util.JSONResponse{ + Code: http.StatusOK, + JSON: loginResponse{ + UserID: dev.UserID, + AccessToken: dev.AccessToken, + HomeServer: serverName, + DeviceID: dev.ID, + }, } - - return } diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index fe4f1efaa..f764bd4d3 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -22,6 +22,7 @@ import ( "github.com/gorilla/mux" appserviceAPI "github.com/matrix-org/dendrite/appservice/api" "github.com/matrix-org/dendrite/clientapi/api" + "github.com/matrix-org/dendrite/clientapi/auth" "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/clientapi/producers" currentstateAPI "github.com/matrix-org/dendrite/currentstateserver/api" @@ -63,6 +64,7 @@ func Setup( stateAPI currentstateAPI.CurrentStateInternalAPI, extRoomsProvider api.ExtraPublicRoomsProvider, ) { + userInteractiveAuth := auth.NewUserInteractive(accountDB.GetAccountByPassword, cfg) publicAPIMux.Handle("/client/versions", httputil.MakeExternalAPI("versions", func(req *http.Request) util.JSONResponse { @@ -629,7 +631,7 @@ func Setup( if err != nil { return util.ErrorResponse(err) } - return DeleteDeviceById(req, deviceDB, device, vars["deviceID"]) + return DeleteDeviceById(req, userInteractiveAuth, deviceDB, device, vars["deviceID"]) }), ).Methods(http.MethodDelete, http.MethodOptions) diff --git a/sytest-whitelist b/sytest-whitelist index 0628ea26e..2d606140c 100644 --- a/sytest-whitelist +++ b/sytest-whitelist @@ -31,6 +31,12 @@ PUT /profile/:user_id/avatar_url sets my avatar GET /profile/:user_id/avatar_url publicly accessible GET /device/{deviceId} gives a 404 for unknown devices PUT /device/{deviceId} gives a 404 for unknown devices +GET /device/{deviceId} +GET /devices +PUT /device/{deviceId} updates device fields +DELETE /device/{deviceId} +DELETE /device/{deviceId} requires UI auth user to match device owner +DELETE /device/{deviceId} with no body gives a 401 POST /createRoom makes a public room POST /createRoom makes a private room POST /createRoom makes a private room with invites From 08e9d996b6e9f726425d815820e3857c12ebc0b3 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 10 Jul 2020 16:28:18 +0100 Subject: [PATCH 12/15] Yggdrasil demo updates Squashed commit of the following: commit 6c2c48f862c1b6f8e741c57804282eceffe02487 Author: Neil Alexander Date: Fri Jul 10 16:28:09 2020 +0100 Add README.md commit 5eeefdadf8e3881dd7a32559a92be49bd7ddaf47 Author: Neil Alexander Date: Fri Jul 10 10:18:50 2020 +0100 Fix wedge in federation sender commit e2ebffbfba25cf82378393940a613ec32bfb909f Merge: 0883ef88 abf26c12 Author: Neil Alexander Date: Fri Jul 10 09:51:23 2020 +0100 Merge branch 'master' into neilalexander/yggdrasil commit 0883ef8870e340f2ae9a0c37ed939dc2ab9911f6 Author: Neil Alexander Date: Fri Jul 10 09:51:06 2020 +0100 Adjust timeouts commit ba2d53199910f13b60cc892debe96a962e8c9acb Author: Neil Alexander Date: Thu Jul 9 16:34:40 2020 +0100 Try to wake up from peers/sessions properly commit 73f42eb494741ba5b0e0cef43654708e3c8eb399 Author: Neil Alexander Date: Thu Jul 9 15:43:38 2020 +0100 Use TransactionWriter to reduce database lock issues on SQLite commit 08bfe63241a18c58c539c91b9f52edccda63a611 Author: Neil Alexander Date: Thu Jul 9 12:38:02 2020 +0100 Un-wedge federation Squashed commit of the following: commit aee933f8785e7a7998105f6090f514d18051a1bd Author: Neil Alexander Date: Thu Jul 9 12:22:41 2020 +0100 Un-goroutine the goroutines commit 478374e5d18a3056cac6682ef9095d41352d1295 Author: Neil Alexander Date: Thu Jul 9 12:09:31 2020 +0100 Reduce federation sender wedges commit 40cc62c54d9e3a863868214c48b7c18e522a4772 Author: Neil Alexander Date: Thu Jul 9 10:02:52 2020 +0100 Handle switching in/out background more reliably --- build/gobind/monolith.go | 80 ++++++++++++------- cmd/dendrite-demo-yggdrasil/README.md | 22 +++++ cmd/dendrite-demo-yggdrasil/main.go | 18 +++-- cmd/dendrite-demo-yggdrasil/yggconn/client.go | 6 +- cmd/dendrite-demo-yggdrasil/yggconn/node.go | 19 ++--- .../yggconn/session.go | 7 ++ federationsender/queue/destinationqueue.go | 7 +- federationsender/storage/sqlite3/storage.go | 40 +++++----- go.mod | 2 +- go.sum | 2 + 10 files changed, 128 insertions(+), 75 deletions(-) create mode 100644 cmd/dendrite-demo-yggdrasil/README.md diff --git a/build/gobind/monolith.go b/build/gobind/monolith.go index d220fabc4..bc638d004 100644 --- a/build/gobind/monolith.go +++ b/build/gobind/monolith.go @@ -3,6 +3,7 @@ package gobind import ( "context" "crypto/tls" + "encoding/hex" "fmt" "net" "net/http" @@ -25,12 +26,17 @@ import ( "github.com/matrix-org/gomatrixserverlib" "github.com/sirupsen/logrus" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "go.uber.org/atomic" ) type DendriteMonolith struct { + logger logrus.Logger YggdrasilNode *yggconn.Node StorageDirectory string listener net.Listener + httpServer *http.Server + httpListening atomic.Bool + yggListening atomic.Bool } func (m *DendriteMonolith) BaseURL() string { @@ -58,9 +64,10 @@ func (m *DendriteMonolith) DisconnectMulticastPeers() { } func (m *DendriteMonolith) Start() { - logger := logrus.Logger{ + m.logger = logrus.Logger{ Out: BindLogger{}, } + m.logger.SetOutput(BindLogger{}) logrus.SetOutput(BindLogger{}) var err error @@ -162,38 +169,39 @@ func (m *DendriteMonolith) Start() { base.UseHTTPAPIs, ) - ygg.NotifySessionNew(func(boxPubKey crypto.BoxPubKey) { - serv := gomatrixserverlib.ServerName(boxPubKey.String()) + ygg.NewSession = func(serverName gomatrixserverlib.ServerName) { + logrus.Infof("Found new session %q", serverName) + time.Sleep(time.Second * 3) req := &api.PerformServersAliveRequest{ - Servers: []gomatrixserverlib.ServerName{serv}, + Servers: []gomatrixserverlib.ServerName{serverName}, } res := &api.PerformServersAliveResponse{} if err := fsAPI.PerformServersAlive(context.TODO(), req, res); err != nil { - logrus.WithError(err).Warnf("Failed to notify server %q alive due to new session", serv) - } else { - logrus.Infof("Notified server %q alive due to new session", serv) + logrus.WithError(err).Warn("Failed to notify server alive due to new session") } - }) + } - ygg.NotifyLinkNew(func(boxPubKey crypto.BoxPubKey, linkType, remote string) { - serv := gomatrixserverlib.ServerName(boxPubKey.String()) + ygg.NotifyLinkNew(func(_ crypto.BoxPubKey, sigPubKey crypto.SigPubKey, linkType, remote string) { + serverName := hex.EncodeToString(sigPubKey[:]) + logrus.Infof("Found new peer %q", serverName) + time.Sleep(time.Second * 3) req := &api.PerformServersAliveRequest{ - Servers: []gomatrixserverlib.ServerName{serv}, + Servers: []gomatrixserverlib.ServerName{ + gomatrixserverlib.ServerName(serverName), + }, } res := &api.PerformServersAliveResponse{} if err := fsAPI.PerformServersAlive(context.TODO(), req, res); err != nil { - logrus.WithError(err).Warnf("Failed to notify server %q alive due to new peer", serv) - } else { - logrus.Infof("Notified server %q alive due to new peer", serv) + logrus.WithError(err).Warn("Failed to notify server alive due to new session") } }) // Build both ends of a HTTP multiplex. - httpServer := &http.Server{ + m.httpServer = &http.Server{ Addr: ":0", TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){}, - ReadTimeout: 15 * time.Second, - WriteTimeout: 45 * time.Second, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, BaseContext: func(_ net.Listener) context.Context { return context.Background() @@ -201,19 +209,33 @@ func (m *DendriteMonolith) Start() { Handler: base.BaseMux, } - go func() { - logger.Info("Listening on ", ygg.DerivedServerName()) - logger.Fatal(httpServer.Serve(ygg)) - }() - go func() { - logger.Info("Listening on ", m.BaseURL()) - logger.Fatal(httpServer.Serve(m.listener)) - }() + m.Resume() } -func (m *DendriteMonolith) Stop() { - if err := m.listener.Close(); err != nil { - logrus.Warn("Error stopping listener:", err) +func (m *DendriteMonolith) Resume() { + logrus.Info("Resuming monolith") + if listener, err := net.Listen("tcp", "localhost:65432"); err == nil { + m.listener = listener + } + if m.yggListening.CAS(false, true) { + go func() { + m.logger.Info("Listening on ", m.YggdrasilNode.DerivedServerName()) + m.logger.Fatal(m.httpServer.Serve(m.YggdrasilNode)) + m.yggListening.Store(false) + }() + } + if m.httpListening.CAS(false, true) { + go func() { + m.logger.Info("Listening on ", m.BaseURL()) + m.logger.Fatal(m.httpServer.Serve(m.listener)) + m.httpListening.Store(false) + }() + } +} + +func (m *DendriteMonolith) Suspend() { + m.logger.Info("Suspending monolith") + if err := m.httpServer.Close(); err != nil { + m.logger.Warn("Error stopping HTTP server:", err) } - m.YggdrasilNode.Stop() } diff --git a/cmd/dendrite-demo-yggdrasil/README.md b/cmd/dendrite-demo-yggdrasil/README.md new file mode 100644 index 000000000..148b9a581 --- /dev/null +++ b/cmd/dendrite-demo-yggdrasil/README.md @@ -0,0 +1,22 @@ +# Yggdrasil Demo + +This is the Dendrite Yggdrasil demo! It's easy to get started - all you need is Go 1.13 or later. + +To run the homeserver, start at the root of the Dendrite repository and run: + +``` +go run ./cmd/dendrite-demo-yggdrasil +``` + +The following command line arguments are accepted: + +* `-peer tcp://a.b.c.d:e` to specify a static Yggdrasil peer to connect to - you will need to supply this if you do not have another Yggdrasil node on your network +* `-port 12345` to specify a port to listen on for client connections + +If you need to find an internet peer, take a look at [this list](https://publicpeers.neilalexander.dev/). + +Then point your favourite Matrix client to the homeserver URL`http://localhost:8008` (or whichever `-port` you specified), create an account and log in. + +If your peering connection is operational then you should see a `Connected TCP:` line in the log output. If not then try a different peer. + +Once logged in, you should be able to open the room directory or join a room by its ID. \ No newline at end of file diff --git a/cmd/dendrite-demo-yggdrasil/main.go b/cmd/dendrite-demo-yggdrasil/main.go index 657477563..122d02663 100644 --- a/cmd/dendrite-demo-yggdrasil/main.go +++ b/cmd/dendrite-demo-yggdrasil/main.go @@ -17,6 +17,7 @@ package main import ( "context" "crypto/tls" + "encoding/hex" "flag" "fmt" "net" @@ -154,27 +155,28 @@ func main() { base.UseHTTPAPIs, ) - ygg.NotifySessionNew(func(boxPubKey crypto.BoxPubKey) { + ygg.NewSession = func(serverName gomatrixserverlib.ServerName) { + logrus.Infof("Found new session %q", serverName) req := &api.PerformServersAliveRequest{ - Servers: []gomatrixserverlib.ServerName{ - gomatrixserverlib.ServerName(boxPubKey.String()), - }, + Servers: []gomatrixserverlib.ServerName{serverName}, } res := &api.PerformServersAliveResponse{} if err := fsAPI.PerformServersAlive(context.TODO(), req, res); err != nil { logrus.WithError(err).Warn("Failed to notify server alive due to new session") } - }) + } - ygg.NotifyLinkNew(func(boxPubKey crypto.BoxPubKey, linkType, remote string) { + ygg.NotifyLinkNew(func(_ crypto.BoxPubKey, sigPubKey crypto.SigPubKey, linkType, remote string) { + serverName := hex.EncodeToString(sigPubKey[:]) + logrus.Infof("Found new peer %q", serverName) req := &api.PerformServersAliveRequest{ Servers: []gomatrixserverlib.ServerName{ - gomatrixserverlib.ServerName(boxPubKey.String()), + gomatrixserverlib.ServerName(serverName), }, } res := &api.PerformServersAliveResponse{} if err := fsAPI.PerformServersAlive(context.TODO(), req, res); err != nil { - logrus.WithError(err).Warn("Failed to notify server alive due to new link") + logrus.WithError(err).Warn("Failed to notify server alive due to new session") } }) diff --git a/cmd/dendrite-demo-yggdrasil/yggconn/client.go b/cmd/dendrite-demo-yggdrasil/yggconn/client.go index 399993e3e..b74468db4 100644 --- a/cmd/dendrite-demo-yggdrasil/yggconn/client.go +++ b/cmd/dendrite-demo-yggdrasil/yggconn/client.go @@ -46,7 +46,8 @@ func (n *Node) CreateClient( tr.RegisterProtocol( "matrix", &yggroundtripper{ inner: &http.Transport{ - ResponseHeaderTimeout: 15 * time.Second, + TLSHandshakeTimeout: 20 * time.Second, + ResponseHeaderTimeout: 10 * time.Second, IdleConnTimeout: 60 * time.Second, DialContext: n.yggdialerctx, }, @@ -62,7 +63,8 @@ func (n *Node) CreateFederationClient( tr.RegisterProtocol( "matrix", &yggroundtripper{ inner: &http.Transport{ - ResponseHeaderTimeout: 15 * time.Second, + TLSHandshakeTimeout: 20 * time.Second, + ResponseHeaderTimeout: 10 * time.Second, IdleConnTimeout: 60 * time.Second, DialContext: n.yggdialerctx, }, diff --git a/cmd/dendrite-demo-yggdrasil/yggconn/node.go b/cmd/dendrite-demo-yggdrasil/yggconn/node.go index 89fb69b53..2bc300c8e 100644 --- a/cmd/dendrite-demo-yggdrasil/yggconn/node.go +++ b/cmd/dendrite-demo-yggdrasil/yggconn/node.go @@ -55,6 +55,7 @@ type Node struct { quicConfig *quic.Config sessions sync.Map // string -> quic.Session incoming chan QUICStream + NewSession func(remote gomatrixserverlib.ServerName) } func (n *Node) BuildName() string { @@ -137,7 +138,7 @@ func Setup(instanceName, storageDirectory string) (*Node, error) { MaxIncomingStreams: 0, MaxIncomingUniStreams: 0, KeepAlive: true, - MaxIdleTimeout: time.Second * 60, + MaxIdleTimeout: time.Minute * 15, HandshakeTimeout: time.Second * 15, } @@ -189,7 +190,9 @@ func (n *Node) PeerCount() int { } func (n *Node) KnownNodes() []gomatrixserverlib.ServerName { - nodemap := map[string]struct{}{} + nodemap := map[string]struct{}{ + "b5ae50589e50991dd9dd7d59c5c5f7a4521e8da5b603b7f57076272abc58b374": struct{}{}, + } for _, peer := range n.core.GetSwitchPeers() { nodemap[hex.EncodeToString(peer.SigningKey[:])] = struct{}{} } @@ -264,18 +267,10 @@ func (n *Node) SetStaticPeer(uri string) error { return nil } -func (n *Node) NotifyLinkNew(f func(boxPubKey crypto.BoxPubKey, linkType, remote string)) { +func (n *Node) NotifyLinkNew(f func(boxPubKey crypto.BoxPubKey, sigPubKey crypto.SigPubKey, linkType, remote string)) { n.core.NotifyLinkNew(f) } -func (n *Node) NotifyLinkGone(f func(boxPubKey crypto.BoxPubKey, linkType, remote string)) { +func (n *Node) NotifyLinkGone(f func(boxPubKey crypto.BoxPubKey, sigPubKey crypto.SigPubKey, linkType, remote string)) { n.core.NotifyLinkGone(f) } - -func (n *Node) NotifySessionNew(f func(boxPubKey crypto.BoxPubKey)) { - n.core.NotifySessionNew(f) -} - -func (n *Node) NotifySessionGone(f func(boxPubKey crypto.BoxPubKey)) { - n.core.NotifySessionGone(f) -} diff --git a/cmd/dendrite-demo-yggdrasil/yggconn/session.go b/cmd/dendrite-demo-yggdrasil/yggconn/session.go index 01cec8136..ff77e64f5 100644 --- a/cmd/dendrite-demo-yggdrasil/yggconn/session.go +++ b/cmd/dendrite-demo-yggdrasil/yggconn/session.go @@ -29,6 +29,7 @@ import ( "time" "github.com/lucas-clemente/quic-go" + "github.com/matrix-org/gomatrixserverlib" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) @@ -56,6 +57,12 @@ func (n *Node) listenFromYgg() { func (n *Node) listenFromQUIC(session quic.Session) { n.sessions.Store(session.RemoteAddr().String(), session) defer n.sessions.Delete(session.RemoteAddr()) + if n.NewSession != nil { + if len(session.ConnectionState().PeerCertificates) == 1 { + subjectName := session.ConnectionState().PeerCertificates[0].Subject.CommonName + go n.NewSession(gomatrixserverlib.ServerName(subjectName)) + } + } for { st, err := session.AcceptStream(context.TODO()) if err != nil { diff --git a/federationsender/queue/destinationqueue.go b/federationsender/queue/destinationqueue.go index 82cb343f3..57c8cff6d 100644 --- a/federationsender/queue/destinationqueue.go +++ b/federationsender/queue/destinationqueue.go @@ -256,7 +256,10 @@ func (oq *destinationQueue) backgroundSend() { // PDUs waiting to be sent. By sending a message into the wake chan, // the next loop iteration will try processing these PDUs again, // subject to the backoff. - oq.notifyPDUs <- true + select { + case oq.notifyPDUs <- true: + default: + } } } else if transaction { // If we successfully sent the transaction then clear out @@ -384,7 +387,7 @@ func (oq *destinationQueue) nextTransaction( // TODO: we should check for 500-ish fails vs 400-ish here, // since we shouldn't queue things indefinitely in response // to a 400-ish error - ctx, cancel = context.WithTimeout(context.Background(), time.Second*15) + ctx, cancel = context.WithTimeout(context.Background(), time.Second*30) defer cancel() _, err = oq.client.SendTransaction(ctx, t) switch err.(type) { diff --git a/federationsender/storage/sqlite3/storage.go b/federationsender/storage/sqlite3/storage.go index 7fe6b65b1..1a4715bfc 100644 --- a/federationsender/storage/sqlite3/storage.go +++ b/federationsender/storage/sqlite3/storage.go @@ -239,39 +239,37 @@ func (d *Database) CleanTransactionPDUs( serverName gomatrixserverlib.ServerName, transactionID gomatrixserverlib.TransactionID, ) error { - var err error - var nids []int64 var deleteNIDs []int64 + nids, err := d.selectQueuePDUs(ctx, nil, serverName, transactionID, 50) + if err != nil { + return fmt.Errorf("d.selectQueuePDUs: %w", err) + } if err = d.queuePDUsWriter.Do(d.db, func(txn *sql.Tx) error { - nids, err = d.selectQueuePDUs(ctx, txn, serverName, transactionID, 50) - if err != nil { - return fmt.Errorf("d.selectQueuePDUs: %w", err) - } if err = d.deleteQueueTransaction(ctx, txn, serverName, transactionID); err != nil { return fmt.Errorf("d.deleteQueueTransaction: %w", err) } - var count int64 - for _, nid := range nids { - count, err = d.selectQueueReferenceJSONCount(ctx, txn, nid) - if err != nil { - return fmt.Errorf("d.selectQueueReferenceJSONCount: %w", err) - } - if count == 0 { - deleteNIDs = append(deleteNIDs, nid) - } - } return nil }); err != nil { return err } - err = d.queueJSONWriter.Do(d.db, func(txn *sql.Tx) error { - if len(deleteNIDs) > 0 { + var count int64 + for _, nid := range nids { + count, err = d.selectQueueReferenceJSONCount(ctx, nil, nid) + if err != nil { + return fmt.Errorf("d.selectQueueReferenceJSONCount: %w", err) + } + if count == 0 { + deleteNIDs = append(deleteNIDs, nid) + } + } + if len(deleteNIDs) > 0 { + err = d.queueJSONWriter.Do(d.db, func(txn *sql.Tx) error { if err = d.deleteQueueJSON(ctx, txn, deleteNIDs); err != nil { return fmt.Errorf("d.deleteQueueJSON: %w", err) } - } - return nil - }) + return nil + }) + } return err } diff --git a/go.mod b/go.mod index 5c896a377..2c348d940 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/uber-go/atomic v1.3.0 // indirect github.com/uber/jaeger-client-go v2.15.0+incompatible github.com/uber/jaeger-lib v1.5.0 - github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200708124809-79077e271c6d + github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200709151813-3c2f73ac5e86 go.uber.org/atomic v1.4.0 golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 golang.org/x/mobile v0.0.0-20200629153529-33b80540585f // indirect diff --git a/go.sum b/go.sum index 1fd01ce9e..e05e69c57 100644 --- a/go.sum +++ b/go.sum @@ -663,6 +663,8 @@ github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200708123331-4e0b0e723459 github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200708123331-4e0b0e723459/go.mod h1:d+Nz6SPeG6kmeSPFL0cvfWfgwEql75fUnZiAONgvyBE= github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200708124809-79077e271c6d h1:ly327dysc3r7lfG+AKJWPSAQmGf4h++fk+Y2dD8nDV4= github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200708124809-79077e271c6d/go.mod h1:d+Nz6SPeG6kmeSPFL0cvfWfgwEql75fUnZiAONgvyBE= +github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200709151813-3c2f73ac5e86 h1:l1zL1Cu/oi8MaBfcKHz4aMdSF5OWOT82SL6y5qP2law= +github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200709151813-3c2f73ac5e86/go.mod h1:d+Nz6SPeG6kmeSPFL0cvfWfgwEql75fUnZiAONgvyBE= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= From 3178afde2c432d5886fa51e1eacfb1e770da39a6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 13 Jul 2020 09:38:40 +0100 Subject: [PATCH 13/15] Update go.mod/go.sum --- go.mod | 3 +-- go.sum | 29 ++--------------------------- 2 files changed, 3 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index 2c348d940..e396ea7d6 100644 --- a/go.mod +++ b/go.mod @@ -36,10 +36,9 @@ require ( github.com/uber-go/atomic v1.3.0 // indirect github.com/uber/jaeger-client-go v2.15.0+incompatible github.com/uber/jaeger-lib v1.5.0 - github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200709151813-3c2f73ac5e86 + github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200713083728-5a765b33d55b go.uber.org/atomic v1.4.0 golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 - golang.org/x/mobile v0.0.0-20200629153529-33b80540585f // indirect gopkg.in/h2non/bimg.v1 v1.0.18 gopkg.in/yaml.v2 v2.2.8 ) diff --git a/go.sum b/go.sum index e05e69c57..b5126268b 100644 --- a/go.sum +++ b/go.sum @@ -12,7 +12,6 @@ github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOv github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0 h1:p3puK8Sl2xK+2FnnIvY/C0N1aqJo2kbEsdAzU+Tnv48= github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/Shopify/sarama v1.26.1 h1:3jnfWKD7gVwbB1KSy/lE0szA9duPuSFLViK0o/d3DgA= github.com/Shopify/sarama v1.26.1/go.mod h1:NbSGBSSndYaIhRcBtY9V0U7AyH+x71bG668AuWys/yU= @@ -422,8 +421,6 @@ github.com/matrix-org/go-sqlite3-js v0.0.0-20200522092705-bc8506ccbcf3 h1:Yb+Wlf github.com/matrix-org/go-sqlite3-js v0.0.0-20200522092705-bc8506ccbcf3/go.mod h1:e+cg2q7C7yE5QnAXgzo512tgFh1RbQLC0+jozuegKgo= github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26 h1:Hr3zjRsq2bhrnp3Ky1qgx/fzCtCALOoGYylh2tpS9K4= github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0= -github.com/matrix-org/gomatrixserverlib v0.0.0-20200708143827-8bfb7222929b h1:mHFK2pcy+fhettE42aoq+JTcj3ysqFqkQLlUpC33Fwg= -github.com/matrix-org/gomatrixserverlib v0.0.0-20200708143827-8bfb7222929b/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= github.com/matrix-org/gomatrixserverlib v0.0.0-20200708152912-d034ccb75e2d h1:v3MGEwCLlFjzZcYJu+aI3kUoNxQyCM3DUurjJFlaI04= github.com/matrix-org/gomatrixserverlib v0.0.0-20200708152912-d034ccb75e2d/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= github.com/matrix-org/naffka v0.0.0-20200422140631-181f1ee7401f h1:pRz4VTiRCO4zPlEMc3ESdUOcW4PXHH4Kj+YDz1XyE+Y= @@ -655,16 +652,8 @@ github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhe github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yggdrasil-network/yggdrasil-extras v0.0.0-20200525205615-6c8a4a2e8855/go.mod h1:xQdsh08Io6nV4WRnOVTe6gI8/2iTvfLDQ0CYa5aMt+I= -github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200707124509-16343a00055c h1:tK1FySfDA5xVT5sAK/3XjUCE9LEoandmVnrg4Hj0fXk= -github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200707124509-16343a00055c/go.mod h1:d+Nz6SPeG6kmeSPFL0cvfWfgwEql75fUnZiAONgvyBE= -github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200708120322-c6245869aeac h1:TMehvDn6TmWemBMxBFQoF/rQ513OmytfDY6sLPz3M64= -github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200708120322-c6245869aeac/go.mod h1:d+Nz6SPeG6kmeSPFL0cvfWfgwEql75fUnZiAONgvyBE= -github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200708123331-4e0b0e723459 h1:B7wypB+eA3xdB9bH/rFNywP3vg0MuIoMb3SojWApt+4= -github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200708123331-4e0b0e723459/go.mod h1:d+Nz6SPeG6kmeSPFL0cvfWfgwEql75fUnZiAONgvyBE= -github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200708124809-79077e271c6d h1:ly327dysc3r7lfG+AKJWPSAQmGf4h++fk+Y2dD8nDV4= -github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200708124809-79077e271c6d/go.mod h1:d+Nz6SPeG6kmeSPFL0cvfWfgwEql75fUnZiAONgvyBE= -github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200709151813-3c2f73ac5e86 h1:l1zL1Cu/oi8MaBfcKHz4aMdSF5OWOT82SL6y5qP2law= -github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200709151813-3c2f73ac5e86/go.mod h1:d+Nz6SPeG6kmeSPFL0cvfWfgwEql75fUnZiAONgvyBE= +github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200713083728-5a765b33d55b h1:py36vWqSnHIQ2DQ9gC0jbkiFd9OCTQX01PdYJ7KmaQE= +github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200713083728-5a765b33d55b/go.mod h1:d+Nz6SPeG6kmeSPFL0cvfWfgwEql75fUnZiAONgvyBE= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= @@ -690,7 +679,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90Pveol golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -703,19 +691,10 @@ golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzhtallWRSm4Q0d09pL6XbQtU= golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20200629153529-33b80540585f h1:9MxnlCHwn6IfUTinHBBzcBhmrX4OXfRmi954tWGKq+M= -golang.org/x/mobile v0.0.0-20200629153529-33b80540585f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -802,11 +781,7 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd h1:/e+gpKk9r3dJobndpTytxS2gOy6m5uvpg+ISQoEcusQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk= -golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= From 396219ef534093b45ad02cccc8ca9cf0f9742c40 Mon Sep 17 00:00:00 2001 From: Kegsay Date: Mon, 13 Jul 2020 16:02:35 +0100 Subject: [PATCH 14/15] Add boilerplate for key server APIs (#1196) Also add a README which outilnes how things will work. --- {keyserver => clientapi}/routing/keys.go | 7 ++ clientapi/routing/routing.go | 13 +++ cmd/dendrite-key-server/main.go | 4 +- cmd/dendrite-monolith-server/main.go | 3 + internal/config/config.go | 11 ++- internal/setup/base.go | 11 +++ internal/setup/monolith.go | 5 +- keyserver/README.md | 19 +++++ keyserver/api/api.go | 49 +++++++++++ keyserver/internal/internal.go | 19 +++++ keyserver/inthttp/client.go | 103 +++++++++++++++++++++++ keyserver/inthttp/server.go | 61 ++++++++++++++ keyserver/keyserver.go | 21 +++-- keyserver/routing/routing.go | 54 ------------ 14 files changed, 312 insertions(+), 68 deletions(-) rename {keyserver => clientapi}/routing/keys.go (88%) create mode 100644 keyserver/README.md create mode 100644 keyserver/api/api.go create mode 100644 keyserver/internal/internal.go create mode 100644 keyserver/inthttp/client.go create mode 100644 keyserver/inthttp/server.go delete mode 100644 keyserver/routing/routing.go diff --git a/keyserver/routing/keys.go b/clientapi/routing/keys.go similarity index 88% rename from keyserver/routing/keys.go rename to clientapi/routing/keys.go index a279a747c..5c1a657fe 100644 --- a/keyserver/routing/keys.go +++ b/clientapi/routing/keys.go @@ -31,3 +31,10 @@ func QueryKeys( }, } } + +func UploadKeys(req *http.Request) util.JSONResponse { + return util.JSONResponse{ + Code: 200, + JSON: struct{}{}, + } +} diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index f764bd4d3..965a46d29 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -695,4 +695,17 @@ func Setup( return GetCapabilities(req, rsAPI) }), ).Methods(http.MethodGet) + + r0mux.Handle("/keys/query", + httputil.MakeAuthAPI("queryKeys", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + return QueryKeys(req) + }), + ).Methods(http.MethodPost, http.MethodOptions) + + // Supplying a device ID is deprecated. + r0mux.Handle("/keys/upload/{deviceID}", + httputil.MakeAuthAPI("keys_upload", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + return UploadKeys(req) + }), + ).Methods(http.MethodPost, http.MethodOptions) } diff --git a/cmd/dendrite-key-server/main.go b/cmd/dendrite-key-server/main.go index b557cbd9e..813ddddc5 100644 --- a/cmd/dendrite-key-server/main.go +++ b/cmd/dendrite-key-server/main.go @@ -24,9 +24,9 @@ func main() { base := setup.NewBaseDendrite(cfg, "KeyServer", true) defer base.Close() // nolint: errcheck - userAPI := base.UserAPIClient() + intAPI := keyserver.NewInternalAPI() - keyserver.AddPublicRoutes(base.PublicAPIMux, base.Cfg, userAPI) + keyserver.AddInternalRoutes(base.InternalAPIMux, intAPI) base.SetupAndServeHTTP(string(base.Cfg.Bind.KeyServer), string(base.Cfg.Listen.KeyServer)) diff --git a/cmd/dendrite-monolith-server/main.go b/cmd/dendrite-monolith-server/main.go index 9ac6941b2..83c49d1e1 100644 --- a/cmd/dendrite-monolith-server/main.go +++ b/cmd/dendrite-monolith-server/main.go @@ -27,6 +27,7 @@ import ( "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/keyserver" "github.com/matrix-org/dendrite/roomserver" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/serverkeyapi" @@ -118,6 +119,7 @@ func main() { rsImpl.SetFederationSenderAPI(fsAPI) stateAPI := currentstateserver.NewInternalAPI(base.Cfg, base.KafkaConsumer) + keyAPI := keyserver.NewInternalAPI() monolith := setup.Monolith{ Config: base.Cfg, @@ -136,6 +138,7 @@ func main() { ServerKeyAPI: serverKeyAPI, StateAPI: stateAPI, UserAPI: userAPI, + KeyAPI: keyAPI, } monolith.AddAllPublicRoutes(base.PublicAPIMux) diff --git a/internal/config/config.go b/internal/config/config.go index 777bd6a39..ac6249d6a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -764,7 +764,7 @@ func (config *Dendrite) FederationSenderURL() string { return "http://" + string(config.Listen.FederationSender) } -// ServerKeyAPIURL returns an HTTP URL for where the federation sender is listening. +// ServerKeyAPIURL returns an HTTP URL for where the server key API is listening. func (config *Dendrite) ServerKeyAPIURL() string { // Hard code the server key API server to talk HTTP for now. // If we support HTTPS we need to think of a practical way to do certificate validation. @@ -773,6 +773,15 @@ func (config *Dendrite) ServerKeyAPIURL() string { return "http://" + string(config.Listen.ServerKeyAPI) } +// KeyServerURL returns an HTTP URL for where the key server is listening. +func (config *Dendrite) KeyServerURL() string { + // Hard code the key server to talk HTTP for now. + // If we support HTTPS we need to think of a practical way to do certificate validation. + // People setting up servers shouldn't need to get a certificate valid for the public + // internet for an internal API. + return "http://" + string(config.Listen.KeyServer) +} + // SetupTracing configures the opentracing using the supplied configuration. func (config *Dendrite) SetupTracing(serviceName string) (closer io.Closer, err error) { if !config.Tracing.Enabled { diff --git a/internal/setup/base.go b/internal/setup/base.go index ddf8e0fad..333c01736 100644 --- a/internal/setup/base.go +++ b/internal/setup/base.go @@ -44,6 +44,8 @@ import ( federationSenderAPI "github.com/matrix-org/dendrite/federationsender/api" fsinthttp "github.com/matrix-org/dendrite/federationsender/inthttp" "github.com/matrix-org/dendrite/internal/config" + keyserverAPI "github.com/matrix-org/dendrite/keyserver/api" + keyinthttp "github.com/matrix-org/dendrite/keyserver/inthttp" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" rsinthttp "github.com/matrix-org/dendrite/roomserver/inthttp" serverKeyAPI "github.com/matrix-org/dendrite/serverkeyapi/api" @@ -214,6 +216,15 @@ func (b *BaseDendrite) ServerKeyAPIClient() serverKeyAPI.ServerKeyInternalAPI { return f } +// KeyServerHTTPClient returns KeyInternalAPI for hitting the key server over HTTP +func (b *BaseDendrite) KeyServerHTTPClient() keyserverAPI.KeyInternalAPI { + f, err := keyinthttp.NewKeyServerClient(b.Cfg.KeyServerURL(), b.httpClient) + if err != nil { + logrus.WithError(err).Panic("KeyServerHTTPClient failed", b.httpClient) + } + return f +} + // CreateDeviceDB creates a new instance of the device database. Should only be // called once per component. func (b *BaseDendrite) CreateDeviceDB() devices.Database { diff --git a/internal/setup/monolith.go b/internal/setup/monolith.go index 6ace53a99..9ae629485 100644 --- a/internal/setup/monolith.go +++ b/internal/setup/monolith.go @@ -26,7 +26,7 @@ import ( federationSenderAPI "github.com/matrix-org/dendrite/federationsender/api" "github.com/matrix-org/dendrite/internal/config" "github.com/matrix-org/dendrite/internal/transactions" - "github.com/matrix-org/dendrite/keyserver" + keyAPI "github.com/matrix-org/dendrite/keyserver/api" "github.com/matrix-org/dendrite/mediaapi" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" serverKeyAPI "github.com/matrix-org/dendrite/serverkeyapi/api" @@ -56,6 +56,7 @@ type Monolith struct { ServerKeyAPI serverKeyAPI.ServerKeyInternalAPI UserAPI userapi.UserInternalAPI StateAPI currentstateAPI.CurrentStateInternalAPI + KeyAPI keyAPI.KeyInternalAPI // Optional ExtPublicRoomsProvider api.ExtraPublicRoomsProvider @@ -69,8 +70,6 @@ func (m *Monolith) AddAllPublicRoutes(publicMux *mux.Router) { m.EDUInternalAPI, m.AppserviceAPI, m.StateAPI, transactions.New(), m.FederationSenderAPI, m.UserAPI, m.ExtPublicRoomsProvider, ) - - keyserver.AddPublicRoutes(publicMux, m.Config, m.UserAPI) federationapi.AddPublicRoutes( publicMux, m.Config, m.UserAPI, m.FedClient, m.KeyRing, m.RoomserverAPI, m.FederationSenderAPI, diff --git a/keyserver/README.md b/keyserver/README.md new file mode 100644 index 000000000..fd9f37d27 --- /dev/null +++ b/keyserver/README.md @@ -0,0 +1,19 @@ +## Key Server + +This is an internal component which manages E2E keys from clients. It handles all the [Key Management APIs](https://matrix.org/docs/spec/client_server/r0.6.1#key-management-api) with the exception of `/keys/changes` which is handled by Sync API. This component is designed to shard by user ID. + +Keys are uploaded and stored in this component, and key changes are emitted to a Kafka topic for downstream components such as Sync API. + +### Internal APIs +- `PerformUploadKeys` stores identity keys and one-time public keys for given user(s). +- `PerformClaimKeys` acquires one-time public keys for given user(s). This may involve outbound federation calls. +- `QueryKeys` returns identity keys for given user(s). This may involve outbound federation calls. This component may then cache federated identity keys to avoid repeatedly hitting remote servers. +- A topic which emits identity keys every time there is a change (addition or deletion). + +### Endpoint mappings +- Client API maps `/keys/upload` to `PerformUploadKeys`. +- Client API maps `/keys/query` to `QueryKeys`. +- Client API maps `/keys/claim` to `PerformClaimKeys`. +- Federation API maps `/user/keys/query` to `QueryKeys`. +- Federation API maps `/user/keys/claim` to `PerformClaimKeys`. +- Sync API maps `/keys/changes` to consuming from the Kafka topic. diff --git a/keyserver/api/api.go b/keyserver/api/api.go new file mode 100644 index 000000000..d8b866f3a --- /dev/null +++ b/keyserver/api/api.go @@ -0,0 +1,49 @@ +// 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 api + +import "context" + +type KeyInternalAPI interface { + PerformUploadKeys(ctx context.Context, req *PerformUploadKeysRequest, res *PerformUploadKeysResponse) + PerformClaimKeys(ctx context.Context, req *PerformClaimKeysRequest, res *PerformClaimKeysResponse) + QueryKeys(ctx context.Context, req *QueryKeysRequest, res *QueryKeysResponse) +} + +// KeyError is returned if there was a problem performing/querying the server +type KeyError struct { + Error string +} + +type PerformUploadKeysRequest struct { +} + +type PerformUploadKeysResponse struct { + Error *KeyError +} + +type PerformClaimKeysRequest struct { +} + +type PerformClaimKeysResponse struct { + Error *KeyError +} + +type QueryKeysRequest struct { +} + +type QueryKeysResponse struct { + Error *KeyError +} diff --git a/keyserver/internal/internal.go b/keyserver/internal/internal.go new file mode 100644 index 000000000..40883cdd6 --- /dev/null +++ b/keyserver/internal/internal.go @@ -0,0 +1,19 @@ +package internal + +import ( + "context" + + "github.com/matrix-org/dendrite/keyserver/api" +) + +type KeyInternalAPI struct{} + +func (a *KeyInternalAPI) PerformUploadKeys(ctx context.Context, req *api.PerformUploadKeysRequest, res *api.PerformUploadKeysResponse) { + +} +func (a *KeyInternalAPI) PerformClaimKeys(ctx context.Context, req *api.PerformClaimKeysRequest, res *api.PerformClaimKeysResponse) { + +} +func (a *KeyInternalAPI) QueryKeys(ctx context.Context, req *api.QueryKeysRequest, res *api.QueryKeysResponse) { + +} diff --git a/keyserver/inthttp/client.go b/keyserver/inthttp/client.go new file mode 100644 index 000000000..f2d00c705 --- /dev/null +++ b/keyserver/inthttp/client.go @@ -0,0 +1,103 @@ +// 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 inthttp + +import ( + "context" + "errors" + "net/http" + + "github.com/matrix-org/dendrite/internal/httputil" + "github.com/matrix-org/dendrite/keyserver/api" + "github.com/opentracing/opentracing-go" +) + +// HTTP paths for the internal HTTP APIs +const ( + PerformUploadKeysPath = "/keyserver/performUploadKeys" + PerformClaimKeysPath = "/keyserver/performClaimKeys" + QueryKeysPath = "/keyserver/queryKeys" +) + +// NewKeyServerClient creates a KeyInternalAPI implemented by talking to a HTTP POST API. +// If httpClient is nil an error is returned +func NewKeyServerClient( + apiURL string, + httpClient *http.Client, +) (api.KeyInternalAPI, error) { + if httpClient == nil { + return nil, errors.New("NewKeyServerClient: httpClient is ") + } + return &httpKeyInternalAPI{ + apiURL: apiURL, + httpClient: httpClient, + }, nil +} + +type httpKeyInternalAPI struct { + apiURL string + httpClient *http.Client +} + +func (h *httpKeyInternalAPI) PerformClaimKeys( + ctx context.Context, + request *api.PerformClaimKeysRequest, + response *api.PerformClaimKeysResponse, +) { + span, ctx := opentracing.StartSpanFromContext(ctx, "PerformClaimKeys") + defer span.Finish() + + apiURL := h.apiURL + PerformClaimKeysPath + err := httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response) + if err != nil { + response.Error = &api.KeyError{ + Error: err.Error(), + } + } +} + +func (h *httpKeyInternalAPI) PerformUploadKeys( + ctx context.Context, + request *api.PerformUploadKeysRequest, + response *api.PerformUploadKeysResponse, +) { + span, ctx := opentracing.StartSpanFromContext(ctx, "PerformUploadKeys") + defer span.Finish() + + apiURL := h.apiURL + PerformUploadKeysPath + err := httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response) + if err != nil { + response.Error = &api.KeyError{ + Error: err.Error(), + } + } +} + +func (h *httpKeyInternalAPI) QueryKeys( + ctx context.Context, + request *api.QueryKeysRequest, + response *api.QueryKeysResponse, +) { + span, ctx := opentracing.StartSpanFromContext(ctx, "QueryKeys") + defer span.Finish() + + apiURL := h.apiURL + QueryKeysPath + err := httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response) + if err != nil { + response.Error = &api.KeyError{ + Error: err.Error(), + } + } +} diff --git a/keyserver/inthttp/server.go b/keyserver/inthttp/server.go new file mode 100644 index 000000000..ec78b6132 --- /dev/null +++ b/keyserver/inthttp/server.go @@ -0,0 +1,61 @@ +// 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 inthttp + +import ( + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + "github.com/matrix-org/dendrite/internal/httputil" + "github.com/matrix-org/dendrite/keyserver/api" + "github.com/matrix-org/util" +) + +func AddRoutes(internalAPIMux *mux.Router, s api.KeyInternalAPI) { + internalAPIMux.Handle(PerformClaimKeysPath, + httputil.MakeInternalAPI("performClaimKeys", func(req *http.Request) util.JSONResponse { + request := api.PerformClaimKeysRequest{} + response := api.PerformClaimKeysResponse{} + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + s.PerformClaimKeys(req.Context(), &request, &response) + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) + internalAPIMux.Handle(PerformUploadKeysPath, + httputil.MakeInternalAPI("performUploadKeys", func(req *http.Request) util.JSONResponse { + request := api.PerformUploadKeysRequest{} + response := api.PerformUploadKeysResponse{} + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + s.PerformUploadKeys(req.Context(), &request, &response) + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) + internalAPIMux.Handle(QueryKeysPath, + httputil.MakeInternalAPI("queryKeys", func(req *http.Request) util.JSONResponse { + request := api.QueryKeysRequest{} + response := api.QueryKeysResponse{} + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + s.QueryKeys(req.Context(), &request, &response) + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) +} diff --git a/keyserver/keyserver.go b/keyserver/keyserver.go index bedc4dfb8..3bb0e462c 100644 --- a/keyserver/keyserver.go +++ b/keyserver/keyserver.go @@ -16,14 +16,19 @@ package keyserver import ( "github.com/gorilla/mux" - "github.com/matrix-org/dendrite/internal/config" - "github.com/matrix-org/dendrite/keyserver/routing" - userapi "github.com/matrix-org/dendrite/userapi/api" + "github.com/matrix-org/dendrite/keyserver/api" + "github.com/matrix-org/dendrite/keyserver/internal" + "github.com/matrix-org/dendrite/keyserver/inthttp" ) -// AddPublicRoutes registers HTTP handlers for CS API calls -func AddPublicRoutes( - router *mux.Router, cfg *config.Dendrite, userAPI userapi.UserInternalAPI, -) { - routing.Setup(router, cfg, userAPI) +// AddInternalRoutes registers HTTP handlers for the internal API. Invokes functions +// on the given input API. +func AddInternalRoutes(router *mux.Router, intAPI api.KeyInternalAPI) { + inthttp.AddRoutes(router, intAPI) +} + +// NewInternalAPI returns a concerete implementation of the internal API. Callers +// can call functions directly on the returned API or via an HTTP interface using AddInternalRoutes. +func NewInternalAPI() api.KeyInternalAPI { + return &internal.KeyInternalAPI{} } diff --git a/keyserver/routing/routing.go b/keyserver/routing/routing.go deleted file mode 100644 index dba43528f..000000000 --- a/keyserver/routing/routing.go +++ /dev/null @@ -1,54 +0,0 @@ -// 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 routing - -import ( - "net/http" - - "github.com/gorilla/mux" - "github.com/matrix-org/dendrite/internal/config" - "github.com/matrix-org/dendrite/internal/httputil" - userapi "github.com/matrix-org/dendrite/userapi/api" - "github.com/matrix-org/util" -) - -const pathPrefixR0 = "/client/r0" - -// Setup registers HTTP handlers with the given ServeMux. It also supplies the given http.Client -// to clients which need to make outbound HTTP requests. -// -// Due to Setup being used to call many other functions, a gocyclo nolint is -// applied: -// nolint: gocyclo -func Setup( - publicAPIMux *mux.Router, cfg *config.Dendrite, userAPI userapi.UserInternalAPI, -) { - r0mux := publicAPIMux.PathPrefix(pathPrefixR0).Subrouter() - - r0mux.Handle("/keys/query", - httputil.MakeAuthAPI("queryKeys", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { - return QueryKeys(req) - }), - ).Methods(http.MethodPost, http.MethodOptions) - - r0mux.Handle("/keys/upload/{keyID}", - httputil.MakeAuthAPI("keys_upload", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { - return util.JSONResponse{ - Code: 200, - JSON: map[string]interface{}{}, - } - }), - ).Methods(http.MethodPost, http.MethodOptions) -} From 5355c289b9853df5c15f844b1f1e68daf1879edf Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 14 Jul 2020 10:47:34 +0100 Subject: [PATCH 15/15] Bump GMSL version to fix sytest --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e396ea7d6..2a60e3c5a 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/matrix-org/go-http-js-libp2p v0.0.0-20200518170932-783164aeeda4 github.com/matrix-org/go-sqlite3-js v0.0.0-20200522092705-bc8506ccbcf3 github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26 - github.com/matrix-org/gomatrixserverlib v0.0.0-20200708152912-d034ccb75e2d + github.com/matrix-org/gomatrixserverlib v0.0.0-20200714093724-6c9a3db6c0ed github.com/matrix-org/naffka v0.0.0-20200422140631-181f1ee7401f github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7 github.com/mattn/go-sqlite3 v2.0.2+incompatible diff --git a/go.sum b/go.sum index b5126268b..b6b60e9f9 100644 --- a/go.sum +++ b/go.sum @@ -421,8 +421,8 @@ github.com/matrix-org/go-sqlite3-js v0.0.0-20200522092705-bc8506ccbcf3 h1:Yb+Wlf github.com/matrix-org/go-sqlite3-js v0.0.0-20200522092705-bc8506ccbcf3/go.mod h1:e+cg2q7C7yE5QnAXgzo512tgFh1RbQLC0+jozuegKgo= github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26 h1:Hr3zjRsq2bhrnp3Ky1qgx/fzCtCALOoGYylh2tpS9K4= github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0= -github.com/matrix-org/gomatrixserverlib v0.0.0-20200708152912-d034ccb75e2d h1:v3MGEwCLlFjzZcYJu+aI3kUoNxQyCM3DUurjJFlaI04= -github.com/matrix-org/gomatrixserverlib v0.0.0-20200708152912-d034ccb75e2d/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= +github.com/matrix-org/gomatrixserverlib v0.0.0-20200714093724-6c9a3db6c0ed h1:b3PPbP+vzI68obhizd9O8/NzpiGOdj3uJXYz9S62PfE= +github.com/matrix-org/gomatrixserverlib v0.0.0-20200714093724-6c9a3db6c0ed/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= github.com/matrix-org/naffka v0.0.0-20200422140631-181f1ee7401f h1:pRz4VTiRCO4zPlEMc3ESdUOcW4PXHH4Kj+YDz1XyE+Y= github.com/matrix-org/naffka v0.0.0-20200422140631-181f1ee7401f/go.mod h1:y0oDTjZDv5SM9a2rp3bl+CU+bvTRINQsdb7YlDql5Go= github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7 h1:ntrLa/8xVzeSs8vHFHK25k0C+NV74sYMJnNSg5NoSRo=