From 5702b84dae2d79bf069063a5f3b7feb664eeb4cd Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 14 Feb 2022 12:59:13 +0100 Subject: [PATCH 01/56] Add User consent configuration Add consentAPIMux --- dendrite-config.yaml | 25 ++++++++++++ setup/base/base.go | 6 ++- setup/config/config_global.go | 76 +++++++++++++++++++++++++++++++++++ setup/monolith.go | 4 +- 4 files changed, 107 insertions(+), 4 deletions(-) diff --git a/dendrite-config.yaml b/dendrite-config.yaml index 38b146d702..2aa0a40697 100644 --- a/dendrite-config.yaml +++ b/dendrite-config.yaml @@ -68,6 +68,31 @@ global: # to other servers and the federation API will not be exposed. disable_federation: false + # Consent tracking configuration + # If either require_at_registration or send_server_notice_to_guest are true, consent + # messages will be sent to the users. + user_consent: + # Require consent when user registers for the first time + require_at_registration: false + # The name to be shown to the user + policy_name: "Privacy policy" + # The directory to search for templates + template_dir: "./templates/privacy" + # The version of the policy. When loading templates, ".gohtml" template is added as a suffix + # e.g: ${template_dir}/1.0.gohtml needs to exist, if this is set to "1.0" + version: "1.0" + # Send a consent message to guest users + send_server_notice_to_guest: false + # Default message to send to users + server_notice_content: + msg_type: "m.text" + body: >- + Please give your consent to the privacy policy at {{ .ConsentURL }}. + # The error message to display if the user hasn't given their consent yet + block_events_error: >- + You can't send any messages until you consent to the privacy policy at + {{ .ConsentURL }}. + # Configuration for NATS JetStream jetstream: # A list of NATS Server addresses to connect to. If none are specified, an diff --git a/setup/base/base.go b/setup/base/base.go index 819fe1ad42..8300850f9a 100644 --- a/setup/base/base.go +++ b/setup/base/base.go @@ -21,6 +21,7 @@ import ( "io" "net" "net/http" + _ "net/http/pprof" "os" "os/signal" "syscall" @@ -56,8 +57,6 @@ import ( userapi "github.com/matrix-org/dendrite/userapi/api" userapiinthttp "github.com/matrix-org/dendrite/userapi/inthttp" "github.com/sirupsen/logrus" - - _ "net/http/pprof" ) // BaseDendrite is a base for creating new instances of dendrite. It parses @@ -74,6 +73,7 @@ type BaseDendrite struct { PublicKeyAPIMux *mux.Router PublicMediaAPIMux *mux.Router PublicWellKnownAPIMux *mux.Router + PublicConsentAPIMux *mux.Router InternalAPIMux *mux.Router SynapseAdminMux *mux.Router UseHTTPAPIs bool @@ -205,6 +205,7 @@ func NewBaseDendrite(cfg *config.Dendrite, componentName string, options ...Base PublicKeyAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.PublicKeyPathPrefix).Subrouter().UseEncodedPath(), PublicMediaAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.PublicMediaPathPrefix).Subrouter().UseEncodedPath(), PublicWellKnownAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.PublicWellKnownPrefix).Subrouter().UseEncodedPath(), + PublicConsentAPIMux: mux.NewRouter().SkipClean(true).PathPrefix("/_matrix").Subrouter().UseEncodedPath(), InternalAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.InternalPathPrefix).Subrouter().UseEncodedPath(), SynapseAdminMux: mux.NewRouter().SkipClean(true).PathPrefix("/_synapse/").Subrouter().UseEncodedPath(), apiHttpClient: &apiClient, @@ -388,6 +389,7 @@ func (b *BaseDendrite) SetupAndServeHTTP( externalRouter.PathPrefix("/_synapse/").Handler(b.SynapseAdminMux) externalRouter.PathPrefix(httputil.PublicMediaPathPrefix).Handler(b.PublicMediaAPIMux) externalRouter.PathPrefix(httputil.PublicWellKnownPrefix).Handler(b.PublicWellKnownAPIMux) + externalRouter.PathPrefix("/_matrix").Handler(b.PublicConsentAPIMux) if internalAddr != NoListener && internalAddr != externalAddr { go func() { diff --git a/setup/config/config_global.go b/setup/config/config_global.go index 6f2306a6d1..016504da62 100644 --- a/setup/config/config_global.go +++ b/setup/config/config_global.go @@ -1,7 +1,10 @@ package config import ( + "fmt" + "html/template" "math/rand" + "path/filepath" "time" "github.com/matrix-org/gomatrixserverlib" @@ -57,6 +60,9 @@ type Global struct { // DNS caching options for all outbound HTTP requests DNSCache DNSCacheOptions `yaml:"dns_cache"` + + // Consent tracking options + UserConsentOptions UserConsentOptions `yaml:"user_consent"` } func (c *Global) Defaults(generate bool) { @@ -72,6 +78,7 @@ func (c *Global) Defaults(generate bool) { c.Metrics.Defaults(generate) c.DNSCache.Defaults() c.Sentry.Defaults() + c.UserConsentOptions.Defaults() } func (c *Global) Verify(configErrs *ConfigErrors, isMonolith bool) { @@ -82,6 +89,7 @@ func (c *Global) Verify(configErrs *ConfigErrors, isMonolith bool) { c.Metrics.Verify(configErrs, isMonolith) c.Sentry.Verify(configErrs, isMonolith) c.DNSCache.Verify(configErrs, isMonolith) + c.UserConsentOptions.Verify(configErrs, isMonolith) } type OldVerifyKeys struct { @@ -195,3 +203,71 @@ func (c *DNSCacheOptions) Verify(configErrs *ConfigErrors, isMonolith bool) { checkPositive(configErrs, "cache_size", int64(c.CacheSize)) checkPositive(configErrs, "cache_lifetime", int64(c.CacheLifetime)) } + +// Consent tracking configuration +// If either require_at_registration or send_server_notice_to_guest are true, consent +// messages will be sent to the users. +type UserConsentOptions struct { + // Require consent when user registers for the first time + RequireAtRegistration bool `yaml:"require_at_registration"` + // The name to be shown to the user + PolicyName string `yaml:"policy_name"` + // The directory to search for *.gohtml templates + TemplateDir string `yaml:"template_dir"` + // The version of the policy. When loading templates, ".gohtml" template is added as a suffix + // e.g: ${template_dir}/1.0.gohtml needs to exist, if this is set to 1.0 + Version string `yaml:"version"` + // Send a consent message to guest users + SendServerNoticeToGuest bool `yaml:"send_server_notice_to_guest"` + // Default message to send to users + ServerNoticeContent struct { + MsgType string `yaml:"msg_type"` + Body string `yaml:"body"` + } `yaml:"server_notice_content"` + // The error message to display if the user hasn't given their consent yet + BlockEventsError string `yaml:"block_events_error"` + // All loaded templates + Templates *template.Template `yaml:"-"` +} + +func (c *UserConsentOptions) Defaults() { + c.RequireAtRegistration = false + c.SendServerNoticeToGuest = false + c.PolicyName = "Privacy Policy" + c.Version = "1.0" + c.TemplateDir = "./templates/privacy" +} + +func (c *UserConsentOptions) Verify(configErrors *ConfigErrors, isMonolith bool) { + if c.Enabled() { + checkNotEmpty(configErrors, "template_dir", c.TemplateDir) + checkNotEmpty(configErrors, "version", c.Version) + checkNotEmpty(configErrors, "policy_name", c.PolicyName) + if len(*configErrors) > 0 { + return + } + + p, err := filepath.Abs(c.TemplateDir) + if err != nil { + configErrors.Add("unable to get template directory") + return + } + + // Read all defined *.gohtml templates + t, err := template.ParseGlob(filepath.Join(p, "*.gohtml")) + if err != nil || t == nil { + configErrors.Add(fmt.Sprintf("unable to read consent templates: %+v", err)) + return + } + c.Templates = t + // Verify we've got a template for the defined version + versionTemplate := c.Templates.Lookup(c.Version + ".gohtml") + if versionTemplate == nil { + configErrors.Add(fmt.Sprintf("unable to load defined '%s' policy template", c.Version)) + } + } +} + +func (c *UserConsentOptions) Enabled() bool { + return c.RequireAtRegistration || c.SendServerNoticeToGuest +} diff --git a/setup/monolith.go b/setup/monolith.go index e6c955222b..1ada17fca1 100644 --- a/setup/monolith.go +++ b/setup/monolith.go @@ -55,9 +55,9 @@ type Monolith struct { } // AddAllPublicRoutes attaches all public paths to the given router -func (m *Monolith) AddAllPublicRoutes(process *process.ProcessContext, csMux, ssMux, keyMux, wkMux, mediaMux, synapseMux *mux.Router) { +func (m *Monolith) AddAllPublicRoutes(process *process.ProcessContext, csMux, ssMux, keyMux, wkMux, mediaMux, synapseMux, consentMux *mux.Router) { clientapi.AddPublicRoutes( - csMux, synapseMux, &m.Config.ClientAPI, m.AccountDB, + csMux, synapseMux, consentMux, &m.Config.ClientAPI, m.AccountDB, m.FedClient, m.RoomserverAPI, m.EDUInternalAPI, m.AppserviceAPI, transactions.New(), m.FederationAPI, m.UserAPI, m.KeyAPI, m.ExtPublicRoomsProvider, From ccc11f94f7d348dd41f8248019cf2b00a093de7d Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 14 Feb 2022 13:00:07 +0100 Subject: [PATCH 02/56] Add consentAPIMux to components --- build/gobind-pinecone/monolith.go | 1 + build/gobind-yggdrasil/monolith.go | 1 + cmd/dendrite-demo-libp2p/main.go | 1 + cmd/dendrite-demo-pinecone/main.go | 1 + cmd/dendrite-demo-yggdrasil/main.go | 1 + cmd/dendrite-monolith-server/main.go | 1 + cmd/dendrite-polylith-multi/personalities/clientapi.go | 2 +- cmd/dendritejs-pinecone/main.go | 1 + cmd/dendritejs/main.go | 1 + 9 files changed, 9 insertions(+), 1 deletion(-) diff --git a/build/gobind-pinecone/monolith.go b/build/gobind-pinecone/monolith.go index 211b8d653c..4662c4ab03 100644 --- a/build/gobind-pinecone/monolith.go +++ b/build/gobind-pinecone/monolith.go @@ -349,6 +349,7 @@ func (m *DendriteMonolith) Start() { base.PublicWellKnownAPIMux, base.PublicMediaAPIMux, base.SynapseAdminMux, + base.PublicConsentAPIMux, ) httpRouter := mux.NewRouter().SkipClean(true).UseEncodedPath() diff --git a/build/gobind-yggdrasil/monolith.go b/build/gobind-yggdrasil/monolith.go index 3d9ba8aa06..f568bad4d6 100644 --- a/build/gobind-yggdrasil/monolith.go +++ b/build/gobind-yggdrasil/monolith.go @@ -156,6 +156,7 @@ func (m *DendriteMonolith) Start() { base.PublicWellKnownAPIMux, base.PublicMediaAPIMux, base.SynapseAdminMux, + base.PublicConsentAPIMux, ) httpRouter := mux.NewRouter() diff --git a/cmd/dendrite-demo-libp2p/main.go b/cmd/dendrite-demo-libp2p/main.go index 7cbd0b6d4e..9d4ff6a8fb 100644 --- a/cmd/dendrite-demo-libp2p/main.go +++ b/cmd/dendrite-demo-libp2p/main.go @@ -194,6 +194,7 @@ func main() { base.Base.PublicWellKnownAPIMux, base.Base.PublicMediaAPIMux, base.Base.SynapseAdminMux, + base.Base.PublicConsentAPIMux, ) if err := mscs.Enable(&base.Base, &monolith); err != nil { logrus.WithError(err).Fatalf("Failed to enable MSCs") diff --git a/cmd/dendrite-demo-pinecone/main.go b/cmd/dendrite-demo-pinecone/main.go index a897dcd1a8..d6e6dcf808 100644 --- a/cmd/dendrite-demo-pinecone/main.go +++ b/cmd/dendrite-demo-pinecone/main.go @@ -222,6 +222,7 @@ func main() { base.PublicWellKnownAPIMux, base.PublicMediaAPIMux, base.SynapseAdminMux, + base.PublicConsentAPIMux, ) wsUpgrader := websocket.Upgrader{ diff --git a/cmd/dendrite-demo-yggdrasil/main.go b/cmd/dendrite-demo-yggdrasil/main.go index 52e69ee593..076485da71 100644 --- a/cmd/dendrite-demo-yggdrasil/main.go +++ b/cmd/dendrite-demo-yggdrasil/main.go @@ -147,6 +147,7 @@ func main() { base.PublicWellKnownAPIMux, base.PublicMediaAPIMux, base.SynapseAdminMux, + base.PublicConsentAPIMux, ) if err := mscs.Enable(base, &monolith); err != nil { logrus.WithError(err).Fatalf("Failed to enable MSCs") diff --git a/cmd/dendrite-monolith-server/main.go b/cmd/dendrite-monolith-server/main.go index 4d0598f3fd..e73f300c6b 100644 --- a/cmd/dendrite-monolith-server/main.go +++ b/cmd/dendrite-monolith-server/main.go @@ -164,6 +164,7 @@ func main() { base.PublicWellKnownAPIMux, base.PublicMediaAPIMux, base.SynapseAdminMux, + base.PublicConsentAPIMux, ) if len(base.Cfg.MSCs.MSCs) > 0 { diff --git a/cmd/dendrite-polylith-multi/personalities/clientapi.go b/cmd/dendrite-polylith-multi/personalities/clientapi.go index bd9f7a109b..6c81bd8898 100644 --- a/cmd/dendrite-polylith-multi/personalities/clientapi.go +++ b/cmd/dendrite-polylith-multi/personalities/clientapi.go @@ -33,7 +33,7 @@ func ClientAPI(base *basepkg.BaseDendrite, cfg *config.Dendrite) { keyAPI := base.KeyServerHTTPClient() clientapi.AddPublicRoutes( - base.PublicClientAPIMux, base.SynapseAdminMux, &base.Cfg.ClientAPI, accountDB, federation, + base.PublicClientAPIMux, base.SynapseAdminMux, base.PublicConsentAPIMux, &base.Cfg.ClientAPI, accountDB, federation, rsAPI, eduInputAPI, asQuery, transactions.New(), fsAPI, userAPI, keyAPI, nil, &cfg.MSCs, ) diff --git a/cmd/dendritejs-pinecone/main.go b/cmd/dendritejs-pinecone/main.go index 62eea78f2c..b758c740c8 100644 --- a/cmd/dendritejs-pinecone/main.go +++ b/cmd/dendritejs-pinecone/main.go @@ -224,6 +224,7 @@ func startup() { base.PublicWellKnownAPIMux, base.PublicMediaAPIMux, base.SynapseAdminMux, + base.PublicConsentAPIMux, ) httpRouter := mux.NewRouter().SkipClean(true).UseEncodedPath() diff --git a/cmd/dendritejs/main.go b/cmd/dendritejs/main.go index 59de07cd0f..b68b4b8cda 100644 --- a/cmd/dendritejs/main.go +++ b/cmd/dendritejs/main.go @@ -236,6 +236,7 @@ func main() { base.PublicKeyAPIMux, base.PublicMediaAPIMux, base.SynapseAdminMux, + base.PublicConsentAPIMux, ) httpRouter := mux.NewRouter().SkipClean(true).UseEncodedPath() From 4da7df5e3e71ab2bae815fd7e728de425828ca45 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 14 Feb 2022 13:01:26 +0100 Subject: [PATCH 03/56] Add consent tracking template --- docs/templates/privacy/1.0.gohtml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 docs/templates/privacy/1.0.gohtml diff --git a/docs/templates/privacy/1.0.gohtml b/docs/templates/privacy/1.0.gohtml new file mode 100644 index 0000000000..54438c4e05 --- /dev/null +++ b/docs/templates/privacy/1.0.gohtml @@ -0,0 +1,26 @@ + + + + Matrix.org Privacy policy + + +{{ if .HasConsented }} +

+ Your base already belong to us. +

+{{ else }} +

+ All your base are belong to us. +

+ {{ if not .PublicVersion }} + +
+ + + + +
+ {{ end }} +{{ end }} + + \ No newline at end of file From ac343861ada38e698743a9157cdd1ccdf843e8f5 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 14 Feb 2022 13:06:36 +0100 Subject: [PATCH 04/56] Add missing form_secret Add tests --- dendrite-config.yaml | 2 + setup/config/config_global.go | 3 + setup/config/config_global_test.go | 110 +++++++++++++++++++++++ setup/config/testdata/privacy/1.0.gohtml | 26 ++++++ 4 files changed, 141 insertions(+) create mode 100644 setup/config/config_global_test.go create mode 100644 setup/config/testdata/privacy/1.0.gohtml diff --git a/dendrite-config.yaml b/dendrite-config.yaml index 2aa0a40697..f6671b4523 100644 --- a/dendrite-config.yaml +++ b/dendrite-config.yaml @@ -72,6 +72,8 @@ global: # If either require_at_registration or send_server_notice_to_guest are true, consent # messages will be sent to the users. user_consent: + # Randomly generated string to be used to calculate the HMAC + form_secret: "superSecretRandomlyGeneratedSecret" # Require consent when user registers for the first time require_at_registration: false # The name to be shown to the user diff --git a/setup/config/config_global.go b/setup/config/config_global.go index 016504da62..ad69a6d5e8 100644 --- a/setup/config/config_global.go +++ b/setup/config/config_global.go @@ -208,6 +208,8 @@ func (c *DNSCacheOptions) Verify(configErrs *ConfigErrors, isMonolith bool) { // If either require_at_registration or send_server_notice_to_guest are true, consent // messages will be sent to the users. type UserConsentOptions struct { + // Randomly generated string to be used to calculate the HMAC + FormSecret string // Require consent when user registers for the first time RequireAtRegistration bool `yaml:"require_at_registration"` // The name to be shown to the user @@ -243,6 +245,7 @@ func (c *UserConsentOptions) Verify(configErrors *ConfigErrors, isMonolith bool) checkNotEmpty(configErrors, "template_dir", c.TemplateDir) checkNotEmpty(configErrors, "version", c.Version) checkNotEmpty(configErrors, "policy_name", c.PolicyName) + checkNotEmpty(configErrors, "form_secret", c.FormSecret) if len(*configErrors) > 0 { return } diff --git a/setup/config/config_global_test.go b/setup/config/config_global_test.go new file mode 100644 index 0000000000..a3ede2d59a --- /dev/null +++ b/setup/config/config_global_test.go @@ -0,0 +1,110 @@ +package config + +import ( + "testing" +) + +func TestUserConsentOptions_Verify(t *testing.T) { + type args struct { + configErrors *ConfigErrors + isMonolith bool + } + tests := []struct { + name string + fields UserConsentOptions + args args + wantErr bool + }{ + { + name: "template dir not set", + fields: UserConsentOptions{ + RequireAtRegistration: true, + }, + args: struct { + configErrors *ConfigErrors + isMonolith bool + }{configErrors: &ConfigErrors{}, isMonolith: true}, + wantErr: true, + }, + { + name: "template dir set", + fields: UserConsentOptions{ + RequireAtRegistration: true, + TemplateDir: "testdata/privacy", + }, + args: struct { + configErrors *ConfigErrors + isMonolith bool + }{configErrors: &ConfigErrors{}, isMonolith: true}, + wantErr: true, + }, + { + name: "policy name not set", + fields: UserConsentOptions{ + RequireAtRegistration: true, + TemplateDir: "testdata/privacy", + }, + args: struct { + configErrors *ConfigErrors + isMonolith bool + }{configErrors: &ConfigErrors{}, isMonolith: true}, + wantErr: true, + }, + { + name: "policy name set", + fields: UserConsentOptions{ + RequireAtRegistration: true, + TemplateDir: "testdata/privacy", + PolicyName: "Privacy policy", + }, + args: struct { + configErrors *ConfigErrors + isMonolith bool + }{configErrors: &ConfigErrors{}, isMonolith: true}, + wantErr: true, + }, + { + name: "version not set", + fields: UserConsentOptions{ + RequireAtRegistration: true, + TemplateDir: "testdata/privacy", + }, + args: struct { + configErrors *ConfigErrors + isMonolith bool + }{configErrors: &ConfigErrors{}, isMonolith: true}, + wantErr: true, + }, + { + name: "everyhing required set", + fields: UserConsentOptions{ + RequireAtRegistration: true, + TemplateDir: "./testdata/privacy", + Version: "1.0", + PolicyName: "Privacy policy", + }, + args: struct { + configErrors *ConfigErrors + isMonolith bool + }{configErrors: &ConfigErrors{}, isMonolith: true}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &UserConsentOptions{ + RequireAtRegistration: tt.fields.RequireAtRegistration, + PolicyName: tt.fields.PolicyName, + Version: tt.fields.Version, + TemplateDir: tt.fields.TemplateDir, + SendServerNoticeToGuest: tt.fields.SendServerNoticeToGuest, + ServerNoticeContent: tt.fields.ServerNoticeContent, + BlockEventsError: tt.fields.BlockEventsError, + } + c.Verify(tt.args.configErrors, tt.args.isMonolith) + if tt.wantErr && tt.args.configErrors == nil { + t.Errorf("expected no errors, got '%+v'", tt.args.configErrors) + } + }) + } +} diff --git a/setup/config/testdata/privacy/1.0.gohtml b/setup/config/testdata/privacy/1.0.gohtml new file mode 100644 index 0000000000..12602fada9 --- /dev/null +++ b/setup/config/testdata/privacy/1.0.gohtml @@ -0,0 +1,26 @@ + + + + Matrix.org Privacy policy + + +{{ if .HasConsented }} +

+ Your base already belong to us. +

+{{ else }} +

+ All your base are belong to us. +

+{{ if not .PublicVersion }} + +
+ + + + +
+{{ end }} +{{ end }} + + \ No newline at end of file From b6ee34918c79cbd82ab9320660fb9387d90ee758 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 14 Feb 2022 13:41:21 +0100 Subject: [PATCH 05/56] Add consent tracking endpoint --- clientapi/clientapi.go | 3 +- clientapi/routing/consent_tracking.go | 47 +++++++++++++++++++++++++++ clientapi/routing/routing.go | 5 ++- 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 clientapi/routing/consent_tracking.go diff --git a/clientapi/clientapi.go b/clientapi/clientapi.go index d678ada961..a6b8113002 100644 --- a/clientapi/clientapi.go +++ b/clientapi/clientapi.go @@ -36,6 +36,7 @@ import ( func AddPublicRoutes( router *mux.Router, synapseAdminRouter *mux.Router, + consentAPIMux *mux.Router, cfg *config.ClientAPI, accountsDB accounts.Database, federation *gomatrixserverlib.FederationClient, @@ -57,7 +58,7 @@ func AddPublicRoutes( } routing.Setup( - router, synapseAdminRouter, cfg, eduInputAPI, rsAPI, asAPI, + router, synapseAdminRouter, consentAPIMux, cfg, eduInputAPI, rsAPI, asAPI, accountsDB, userAPI, federation, syncProducer, transactionsCache, fsAPI, keyAPI, extRoomsProvider, mscCfg, ) diff --git a/clientapi/routing/consent_tracking.go b/clientapi/routing/consent_tracking.go new file mode 100644 index 0000000000..5947855d4c --- /dev/null +++ b/clientapi/routing/consent_tracking.go @@ -0,0 +1,47 @@ +package routing + +import ( + "net/http" + + "github.com/matrix-org/dendrite/setup/config" + userapi "github.com/matrix-org/dendrite/userapi/api" + "github.com/sirupsen/logrus" +) + +// The data used to populate the /consent request +type constentTemplateData struct { + User string + Version string + UserHMAC string + HasConsented bool + PublicVersion bool +} + +func consent(userAPI userapi.UserInternalAPI, cfg *config.ClientAPI) http.HandlerFunc { + consentCfg := cfg.Matrix.UserConsentOptions + return func(writer http.ResponseWriter, req *http.Request) { + if !consentCfg.Enabled() { + writer.WriteHeader(http.StatusBadRequest) + _, _ = writer.Write([]byte("consent tracking is disabled")) + return + } + switch req.Method { + case http.MethodGet: + // The data used to populate the /consent request + data := constentTemplateData{ + User: req.FormValue("u"), + Version: req.FormValue("v"), + UserHMAC: req.FormValue("h"), + } + // display the privacy policy without a form + data.PublicVersion = data.User == "" || data.UserHMAC == "" || data.Version == "" + + err := consentCfg.Templates.ExecuteTemplate(writer, consentCfg.Version+".gohtml", data) + if err != nil { + logrus.WithError(err).Error("unable to print consent template") + } + case http.MethodPost: + + } + } +} diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index 7320661666..d10817d1b8 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -47,7 +47,7 @@ import ( // applied: // nolint: gocyclo func Setup( - publicAPIMux, synapseAdminRouter *mux.Router, cfg *config.ClientAPI, + publicAPIMux, synapseAdminRouter, consentAPIMux *mux.Router, cfg *config.ClientAPI, eduAPI eduServerAPI.EDUServerInputAPI, rsAPI roomserverAPI.RoomserverInternalAPI, asAPI appserviceAPI.AppServiceQueryAPI, @@ -117,6 +117,9 @@ func Setup( ).Methods(http.MethodGet, http.MethodPost, http.MethodOptions) } + // unspecced consent tracking + consentAPIMux.HandleFunc("/consent", consent(userAPI, cfg)).Methods(http.MethodGet, http.MethodPost, http.MethodOptions) + r0mux := publicAPIMux.PathPrefix("/r0").Subrouter() unstableMux := publicAPIMux.PathPrefix("/unstable").Subrouter() From 9583784e8a1110c5cb466f5a44736df9e6c113ce Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 14 Feb 2022 14:02:13 +0100 Subject: [PATCH 06/56] Add new coloumn to track accepted policy version --- userapi/storage/accounts/interface.go | 2 + .../accounts/postgres/accounts_table.go | 48 ++++++++++++++++++- userapi/storage/accounts/postgres/storage.go | 18 +++++++ .../accounts/sqlite3/accounts_table.go | 47 +++++++++++++++++- userapi/storage/accounts/sqlite3/storage.go | 18 +++++++ 5 files changed, 130 insertions(+), 3 deletions(-) diff --git a/userapi/storage/accounts/interface.go b/userapi/storage/accounts/interface.go index f03b3774ca..e172c09515 100644 --- a/userapi/storage/accounts/interface.go +++ b/userapi/storage/accounts/interface.go @@ -52,6 +52,8 @@ type Database interface { DeactivateAccount(ctx context.Context, localpart string) (err error) CreateOpenIDToken(ctx context.Context, token, localpart string) (exp int64, err error) GetOpenIDTokenAttributes(ctx context.Context, token string) (*api.OpenIDTokenAttributes, error) + GetPrivacyPolicy(ctx context.Context, localpart string) (policyVersion string, err error) + GetOutdatedPolicy(ctx context.Context, policyVersion string) (userIDs []string, err error) // Key backups CreateKeyBackup(ctx context.Context, userID, algorithm string, authData json.RawMessage) (version string, err error) diff --git a/userapi/storage/accounts/postgres/accounts_table.go b/userapi/storage/accounts/postgres/accounts_table.go index b57aa901fe..874b8abcfa 100644 --- a/userapi/storage/accounts/postgres/accounts_table.go +++ b/userapi/storage/accounts/postgres/accounts_table.go @@ -39,9 +39,11 @@ CREATE TABLE IF NOT EXISTS account_accounts ( -- Identifies which application service this account belongs to, if any. appservice_id TEXT, -- If the account is currently active - is_deactivated BOOLEAN DEFAULT FALSE + is_deactivated BOOLEAN DEFAULT FALSE, + -- The policy version this user has accepted + policy_version TEXT -- TODO: - -- is_guest, is_admin, upgraded_ts, devices, any email reset stuff? + -- is_guest, is_admin, upgraded_ts, devices, any email reset stuff? ); -- Create sequence for autogenerated numeric usernames CREATE SEQUENCE IF NOT EXISTS numeric_username_seq START 1; @@ -65,6 +67,12 @@ const selectPasswordHashSQL = "" + const selectNewNumericLocalpartSQL = "" + "SELECT nextval('numeric_username_seq')" +const selectPrivacyPolicySQL = "" + + "SELECT policy_version FROM accounts_accounts WHERE localpart = $1" + +const batchSelectPrivacyPolicySQL = "" + + "SELECT localpart FROM accounts_accounts WHERE policy_version IS NULL or policy_version <> $1" + type accountsStatements struct { insertAccountStmt *sql.Stmt updatePasswordStmt *sql.Stmt @@ -72,6 +80,8 @@ type accountsStatements struct { selectAccountByLocalpartStmt *sql.Stmt selectPasswordHashStmt *sql.Stmt selectNewNumericLocalpartStmt *sql.Stmt + selectPrivacyPolicyStmt *sql.Stmt + batchSelectPrivacyPolicyStmt *sql.Stmt serverName gomatrixserverlib.ServerName } @@ -89,6 +99,8 @@ func (s *accountsStatements) prepare(db *sql.DB, server gomatrixserverlib.Server {&s.selectAccountByLocalpartStmt, selectAccountByLocalpartSQL}, {&s.selectPasswordHashStmt, selectPasswordHashSQL}, {&s.selectNewNumericLocalpartStmt, selectNewNumericLocalpartSQL}, + {&s.selectPrivacyPolicyStmt, selectPrivacyPolicySQL}, + {&s.batchSelectPrivacyPolicyStmt, batchSelectPrivacyPolicySQL}, }.Prepare(db) } @@ -174,3 +186,35 @@ func (s *accountsStatements) selectNewNumericLocalpart( err = stmt.QueryRowContext(ctx).Scan(&id) return } + +// selectPrivacyPolicy gets the current privacy policy a specific user accepted +func (s *accountsStatements) selectPrivacyPolicy( + ctx context.Context, txn *sql.Tx, localPart string, +) (policy string, err error) { + stmt := s.selectPrivacyPolicyStmt + if txn != nil { + stmt = sqlutil.TxStmt(txn, stmt) + } + err = stmt.QueryRowContext(ctx, localPart).Scan(&policy) + return +} + +// batchSelectPrivacyPolicy queries all users which didn't accept the current policy version +func (s *accountsStatements) batchSelectPrivacyPolicy( + ctx context.Context, txn *sql.Tx, policyVersion string, +) (userIDs []string, err error) { + stmt := s.batchSelectPrivacyPolicyStmt + if txn != nil { + stmt = sqlutil.TxStmt(txn, stmt) + } + rows, err := stmt.QueryContext(ctx, policyVersion) + defer rows.Close() + for rows.Next() { + var userID string + if err := rows.Scan(&userID); err != nil { + return userIDs, err + } + userIDs = append(userIDs, userID) + } + return userIDs, rows.Err() +} diff --git a/userapi/storage/accounts/postgres/storage.go b/userapi/storage/accounts/postgres/storage.go index 2f8290623f..b128f450c3 100644 --- a/userapi/storage/accounts/postgres/storage.go +++ b/userapi/storage/accounts/postgres/storage.go @@ -518,3 +518,21 @@ func (d *Database) UpsertBackupKeys( }) return } + +// GetPrivacyPolicy returns the accepted privacy policy version, if any. +func (d *Database) GetPrivacyPolicy(ctx context.Context, localpart string) (policyVersion string, err error) { + err = d.writer.Do(d.db, nil, func(txn *sql.Tx) error { + policyVersion, err = d.accounts.selectPrivacyPolicy(ctx, txn, localpart) + return err + }) + return +} + +// GetOutdatedPolicy queries all users which didn't accept the current policy version +func (d *Database) GetOutdatedPolicy(ctx context.Context, policyVersion string) (userIDs []string, err error) { + err = d.writer.Do(d.db, nil, func(txn *sql.Tx) error { + userIDs, err = d.accounts.batchSelectPrivacyPolicy(ctx, txn, policyVersion) + return err + }) + return +} diff --git a/userapi/storage/accounts/sqlite3/accounts_table.go b/userapi/storage/accounts/sqlite3/accounts_table.go index 8a7c8fba7c..eab6114f1c 100644 --- a/userapi/storage/accounts/sqlite3/accounts_table.go +++ b/userapi/storage/accounts/sqlite3/accounts_table.go @@ -39,7 +39,9 @@ CREATE TABLE IF NOT EXISTS account_accounts ( -- Identifies which application service this account belongs to, if any. appservice_id TEXT, -- If the account is currently active - is_deactivated BOOLEAN DEFAULT 0 + is_deactivated BOOLEAN DEFAULT 0, + -- The policy version this user has accepted + policy_version TEXT -- TODO: -- is_guest, is_admin, upgraded_ts, devices, any email reset stuff? ); @@ -63,6 +65,12 @@ const selectPasswordHashSQL = "" + const selectNewNumericLocalpartSQL = "" + "SELECT COUNT(localpart) FROM account_accounts" +const selectPrivacyPolicySQL = "" + + "SELECT policy_version FROM accounts_accounts WHERE localpart = $1" + +const batchSelectPrivacyPolicySQL = "" + + "SELECT localpart FROM accounts_accounts WHERE policy_version IS NULL or policy_version <> $1" + type accountsStatements struct { db *sql.DB insertAccountStmt *sql.Stmt @@ -71,6 +79,8 @@ type accountsStatements struct { selectAccountByLocalpartStmt *sql.Stmt selectPasswordHashStmt *sql.Stmt selectNewNumericLocalpartStmt *sql.Stmt + selectPrivacyPolicyStmt *sql.Stmt + batchSelectPrivacyPolicyStmt *sql.Stmt serverName gomatrixserverlib.ServerName } @@ -89,6 +99,8 @@ func (s *accountsStatements) prepare(db *sql.DB, server gomatrixserverlib.Server {&s.selectAccountByLocalpartStmt, selectAccountByLocalpartSQL}, {&s.selectPasswordHashStmt, selectPasswordHashSQL}, {&s.selectNewNumericLocalpartStmt, selectNewNumericLocalpartSQL}, + {&s.selectPrivacyPolicyStmt, selectPrivacyPolicySQL}, + {&s.batchSelectPrivacyPolicyStmt, batchSelectPrivacyPolicySQL}, }.Prepare(db) } @@ -174,3 +186,36 @@ func (s *accountsStatements) selectNewNumericLocalpart( err = stmt.QueryRowContext(ctx).Scan(&id) return } + +// selectPrivacyPolicy gets the current privacy policy a specific user accepted + +func (s *accountsStatements) selectPrivacyPolicy( + ctx context.Context, txn *sql.Tx, localPart string, +) (policy string, err error) { + stmt := s.selectPrivacyPolicyStmt + if txn != nil { + stmt = sqlutil.TxStmt(txn, stmt) + } + err = stmt.QueryRowContext(ctx, localPart).Scan(&policy) + return +} + +// batchSelectPrivacyPolicy queries all users which didn't accept the current policy version +func (s *accountsStatements) batchSelectPrivacyPolicy( + ctx context.Context, txn *sql.Tx, policyVersion string, +) (userIDs []string, err error) { + stmt := s.batchSelectPrivacyPolicyStmt + if txn != nil { + stmt = sqlutil.TxStmt(txn, stmt) + } + rows, err := stmt.QueryContext(ctx, policyVersion) + defer rows.Close() + for rows.Next() { + var userID string + if err := rows.Scan(&userID); err != nil { + return userIDs, err + } + userIDs = append(userIDs, userID) + } + return userIDs, rows.Err() +} diff --git a/userapi/storage/accounts/sqlite3/storage.go b/userapi/storage/accounts/sqlite3/storage.go index 2b731b7595..911492d539 100644 --- a/userapi/storage/accounts/sqlite3/storage.go +++ b/userapi/storage/accounts/sqlite3/storage.go @@ -561,3 +561,21 @@ func (d *Database) UpsertBackupKeys( }) return } + +// GetPrivacyPolicy returns the accepted privacy policy version, if any. +func (d *Database) GetPrivacyPolicy(ctx context.Context, localpart string) (policyVersion string, err error) { + err = d.writer.Do(d.db, nil, func(txn *sql.Tx) error { + policyVersion, err = d.accounts.selectPrivacyPolicy(ctx, txn, localpart) + return err + }) + return +} + +// GetOutdatedPolicy queries all users which didn't accept the current policy version +func (d *Database) GetOutdatedPolicy(ctx context.Context, policyVersion string) (userIDs []string, err error) { + err = d.writer.Do(d.db, nil, func(txn *sql.Tx) error { + userIDs, err = d.accounts.batchSelectPrivacyPolicy(ctx, txn, policyVersion) + return err + }) + return +} From 3c5c3ea7fb8e464eae81cf601611520f4a0b4eab Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 14 Feb 2022 14:03:30 +0100 Subject: [PATCH 07/56] Add methods to query the policy version --- userapi/api/api.go | 22 ++++++++++++++++++++++ userapi/api/api_trace.go | 12 ++++++++++++ userapi/internal/api.go | 28 ++++++++++++++++++++++++++++ userapi/inthttp/client.go | 34 ++++++++++++++++++++++++++-------- userapi/inthttp/server.go | 28 ++++++++++++++++++++++++++++ 5 files changed, 116 insertions(+), 8 deletions(-) diff --git a/userapi/api/api.go b/userapi/api/api.go index 46a13d971a..e5a173cec6 100644 --- a/userapi/api/api.go +++ b/userapi/api/api.go @@ -44,6 +44,8 @@ type UserInternalAPI interface { QueryDeviceInfos(ctx context.Context, req *QueryDeviceInfosRequest, res *QueryDeviceInfosResponse) error QuerySearchProfiles(ctx context.Context, req *QuerySearchProfilesRequest, res *QuerySearchProfilesResponse) error QueryOpenIDToken(ctx context.Context, req *QueryOpenIDTokenRequest, res *QueryOpenIDTokenResponse) error + QueryPolicyVersion(ctx context.Context, req *QueryPolicyVersionRequest, res *QueryPolicyVersionResponse) error + GetOutdatedPolicy(ctx context.Context, req *QueryOutdatedPolicyUsersRequest, res *QueryOutdatedPolicyUsersResponse) error } type PerformKeyBackupRequest struct { @@ -335,6 +337,26 @@ type QueryOpenIDTokenResponse struct { ExpiresAtMS int64 } +// QueryPolicyVersionRequest is the response for QueryPolicyVersionRequest +type QueryPolicyVersionRequest struct { + LocalPart string +} + +// QueryPolicyVersionResponsestruct is the response for QueryPolicyVersionResponsestruct +type QueryPolicyVersionResponse struct { + PolicyVersion string +} + +// QueryOutdatedPolicyUsersRequest is the response for QueryOutdatedPolicyUsersRequest +type QueryOutdatedPolicyUsersRequest struct { + PolicyVersion string +} + +// QueryOutdatedPolicyUsersResponse is the response for QueryOutdatedPolicyUsersRequest +type QueryOutdatedPolicyUsersResponse struct { + OutdatedUsers []string +} + // Device represents a client's device (mobile, web, etc) type Device struct { ID string diff --git a/userapi/api/api_trace.go b/userapi/api/api_trace.go index aa069f40b0..15b5572f4b 100644 --- a/userapi/api/api_trace.go +++ b/userapi/api/api_trace.go @@ -119,6 +119,18 @@ func (t *UserInternalAPITrace) QueryOpenIDToken(ctx context.Context, req *QueryO return err } +func (t *UserInternalAPITrace) QueryPolicyVersion(ctx context.Context, req *QueryPolicyVersionRequest, res *QueryPolicyVersionResponse) error { + err := t.Impl.QueryPolicyVersion(ctx, req, res) + util.GetLogger(ctx).Infof("QueryPolicyVersion req=%+v res=%+v", js(req), js(res)) + return err +} + +func (t *UserInternalAPITrace) GetOutdatedPolicy(ctx context.Context, req *QueryOutdatedPolicyUsersRequest, res *QueryOutdatedPolicyUsersResponse) error { + err := t.Impl.GetOutdatedPolicy(ctx, req, res) + util.GetLogger(ctx).Infof("QueryPolicyVersion req=%+v res=%+v", js(req), js(res)) + return err +} + func js(thing interface{}) string { b, err := json.Marshal(thing) if err != nil { diff --git a/userapi/internal/api.go b/userapi/internal/api.go index 5d91383dee..0d7a723a97 100644 --- a/userapi/internal/api.go +++ b/userapi/internal/api.go @@ -587,3 +587,31 @@ func (a *UserInternalAPI) QueryKeyBackup(ctx context.Context, req *api.QueryKeyB } res.Keys = result } + +func (a *UserInternalAPI) QueryPolicyVersion( + ctx context.Context, + req *api.QueryPolicyVersionRequest, + res *api.QueryPolicyVersionResponse, +) error { + var err error + res.PolicyVersion, err = a.AccountDB.GetPrivacyPolicy(ctx, req.LocalPart) + if err != nil { + return err + } + + return nil +} + +func (a *UserInternalAPI) GetOutdatedPolicy( + ctx context.Context, + req *api.QueryOutdatedPolicyUsersRequest, + res *api.QueryOutdatedPolicyUsersResponse, +) error { + var err error + res.OutdatedUsers, err = a.AccountDB.GetOutdatedPolicy(ctx, req.PolicyVersion) + if err != nil { + return err + } + + return nil +} diff --git a/userapi/inthttp/client.go b/userapi/inthttp/client.go index 1599d46398..4a99d1b13c 100644 --- a/userapi/inthttp/client.go +++ b/userapi/inthttp/client.go @@ -38,14 +38,16 @@ const ( PerformOpenIDTokenCreationPath = "/userapi/performOpenIDTokenCreation" PerformKeyBackupPath = "/userapi/performKeyBackup" - QueryKeyBackupPath = "/userapi/queryKeyBackup" - QueryProfilePath = "/userapi/queryProfile" - QueryAccessTokenPath = "/userapi/queryAccessToken" - QueryDevicesPath = "/userapi/queryDevices" - QueryAccountDataPath = "/userapi/queryAccountData" - QueryDeviceInfosPath = "/userapi/queryDeviceInfos" - QuerySearchProfilesPath = "/userapi/querySearchProfiles" - QueryOpenIDTokenPath = "/userapi/queryOpenIDToken" + QueryKeyBackupPath = "/userapi/queryKeyBackup" + QueryProfilePath = "/userapi/queryProfile" + QueryAccessTokenPath = "/userapi/queryAccessToken" + QueryDevicesPath = "/userapi/queryDevices" + QueryAccountDataPath = "/userapi/queryAccountData" + QueryDeviceInfosPath = "/userapi/queryDeviceInfos" + QuerySearchProfilesPath = "/userapi/querySearchProfiles" + QueryOpenIDTokenPath = "/userapi/queryOpenIDToken" + QueryPolicyVersion = "/userapi/queryPolicyVersion" + QueryOutdatedPolicyUsers = "/userapi/queryOutdatedPolicy" ) // NewUserAPIClient creates a UserInternalAPI implemented by talking to a HTTP POST API. @@ -249,3 +251,19 @@ func (h *httpUserInternalAPI) QueryKeyBackup(ctx context.Context, req *api.Query res.Error = err.Error() } } + +func (h *httpUserInternalAPI) QueryPolicyVersion(ctx context.Context, req *api.QueryPolicyVersionRequest, res *api.QueryPolicyVersionResponse) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "QueryKeyBackup") + defer span.Finish() + + apiURL := h.apiURL + QueryPolicyVersion + return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) +} + +func (h *httpUserInternalAPI) GetOutdatedPolicy(ctx context.Context, req *api.QueryOutdatedPolicyUsersRequest, res *api.QueryOutdatedPolicyUsersResponse) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "QueryKeyBackup") + defer span.Finish() + + apiURL := h.apiURL + QueryOutdatedPolicyUsers + return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) +} diff --git a/userapi/inthttp/server.go b/userapi/inthttp/server.go index d00ee042c3..22e49181ce 100644 --- a/userapi/inthttp/server.go +++ b/userapi/inthttp/server.go @@ -265,4 +265,32 @@ func AddRoutes(internalAPIMux *mux.Router, s api.UserInternalAPI) { return util.JSONResponse{Code: http.StatusOK, JSON: &response} }), ) + internalAPIMux.Handle(QueryPolicyVersion, + httputil.MakeInternalAPI("queryPolicyVersion", func(req *http.Request) util.JSONResponse { + request := api.QueryPolicyVersionRequest{} + response := api.QueryPolicyVersionResponse{} + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + err := s.QueryPolicyVersion(req.Context(), &request, &response) + if err != nil { + return util.JSONResponse{Code: http.StatusBadRequest, JSON: &response} + } + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) + internalAPIMux.Handle(QueryOutdatedPolicyUsers, + httputil.MakeInternalAPI("queryOutdatedPolicyUsers", func(req *http.Request) util.JSONResponse { + request := api.QueryOutdatedPolicyUsersRequest{} + response := api.QueryOutdatedPolicyUsersResponse{} + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + err := s.GetOutdatedPolicy(req.Context(), &request, &response) + if err != nil { + return util.JSONResponse{Code: http.StatusBadRequest, JSON: &response} + } + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) } From a505471c90895b4e340a0989fe5e4bbe1b42c101 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 14 Feb 2022 14:52:16 +0100 Subject: [PATCH 08/56] Add table migrations --- .../accounts/postgres/accounts_table.go | 4 +-- .../deltas/20200929203058_is_active.go | 1 + .../2022021414375800_add_policy_version.go | 28 +++++++++++++++++++ userapi/storage/accounts/postgres/storage.go | 1 + .../accounts/sqlite3/accounts_table.go | 4 +-- .../deltas/20200929203058_is_active.go | 1 + .../2022021414375800_add_policy_version.go | 28 +++++++++++++++++++ userapi/storage/accounts/sqlite3/storage.go | 1 + 8 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 userapi/storage/accounts/postgres/deltas/2022021414375800_add_policy_version.go create mode 100644 userapi/storage/accounts/sqlite3/deltas/2022021414375800_add_policy_version.go diff --git a/userapi/storage/accounts/postgres/accounts_table.go b/userapi/storage/accounts/postgres/accounts_table.go index 874b8abcfa..db8cdecdf0 100644 --- a/userapi/storage/accounts/postgres/accounts_table.go +++ b/userapi/storage/accounts/postgres/accounts_table.go @@ -68,10 +68,10 @@ const selectNewNumericLocalpartSQL = "" + "SELECT nextval('numeric_username_seq')" const selectPrivacyPolicySQL = "" + - "SELECT policy_version FROM accounts_accounts WHERE localpart = $1" + "SELECT policy_version FROM account_accounts WHERE localpart = $1" const batchSelectPrivacyPolicySQL = "" + - "SELECT localpart FROM accounts_accounts WHERE policy_version IS NULL or policy_version <> $1" + "SELECT localpart FROM account_accounts WHERE policy_version IS NULL or policy_version <> $1" type accountsStatements struct { insertAccountStmt *sql.Stmt diff --git a/userapi/storage/accounts/postgres/deltas/20200929203058_is_active.go b/userapi/storage/accounts/postgres/deltas/20200929203058_is_active.go index 9e14286e07..1d50d3d5a5 100644 --- a/userapi/storage/accounts/postgres/deltas/20200929203058_is_active.go +++ b/userapi/storage/accounts/postgres/deltas/20200929203058_is_active.go @@ -10,6 +10,7 @@ import ( func LoadFromGoose() { goose.AddMigration(UpIsActive, DownIsActive) + goose.AddMigration(UpAddPolicyVersion, DownAddPolicyVersion) } func LoadIsActive(m *sqlutil.Migrations) { diff --git a/userapi/storage/accounts/postgres/deltas/2022021414375800_add_policy_version.go b/userapi/storage/accounts/postgres/deltas/2022021414375800_add_policy_version.go new file mode 100644 index 0000000000..8cbbe6f330 --- /dev/null +++ b/userapi/storage/accounts/postgres/deltas/2022021414375800_add_policy_version.go @@ -0,0 +1,28 @@ +package deltas + +import ( + "database/sql" + "fmt" + + "github.com/matrix-org/dendrite/internal/sqlutil" +) + +func LoadAddPolicyVersion(m *sqlutil.Migrations) { + m.AddMigration(UpAddPolicyVersion, DownAddPolicyVersion) +} + +func UpAddPolicyVersion(tx *sql.Tx) error { + _, err := tx.Exec("ALTER TABLE account_accounts ADD COLUMN IF NOT EXISTS policy_version TEXT;") + if err != nil { + return fmt.Errorf("failed to execute upgrade: %w", err) + } + return nil +} + +func DownAddPolicyVersion(tx *sql.Tx) error { + _, err := tx.Exec("ALTER TABLE account_accounts DROP COLUMN policy_version;") + if err != nil { + return fmt.Errorf("failed to execute downgrade: %w", err) + } + return nil +} diff --git a/userapi/storage/accounts/postgres/storage.go b/userapi/storage/accounts/postgres/storage.go index b128f450c3..ff91ae4473 100644 --- a/userapi/storage/accounts/postgres/storage.go +++ b/userapi/storage/accounts/postgres/storage.go @@ -73,6 +73,7 @@ func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserver } m := sqlutil.NewMigrations() deltas.LoadIsActive(m) + deltas.LoadAddPolicyVersion(m) if err = m.RunDeltas(db, dbProperties); err != nil { return nil, err } diff --git a/userapi/storage/accounts/sqlite3/accounts_table.go b/userapi/storage/accounts/sqlite3/accounts_table.go index eab6114f1c..95b9eb7bf3 100644 --- a/userapi/storage/accounts/sqlite3/accounts_table.go +++ b/userapi/storage/accounts/sqlite3/accounts_table.go @@ -66,10 +66,10 @@ const selectNewNumericLocalpartSQL = "" + "SELECT COUNT(localpart) FROM account_accounts" const selectPrivacyPolicySQL = "" + - "SELECT policy_version FROM accounts_accounts WHERE localpart = $1" + "SELECT policy_version FROM account_accounts WHERE localpart = $1" const batchSelectPrivacyPolicySQL = "" + - "SELECT localpart FROM accounts_accounts WHERE policy_version IS NULL or policy_version <> $1" + "SELECT localpart FROM account_accounts WHERE policy_version IS NULL or policy_version <> $1" type accountsStatements struct { db *sql.DB diff --git a/userapi/storage/accounts/sqlite3/deltas/20200929203058_is_active.go b/userapi/storage/accounts/sqlite3/deltas/20200929203058_is_active.go index 9fddb05a14..0b95b49969 100644 --- a/userapi/storage/accounts/sqlite3/deltas/20200929203058_is_active.go +++ b/userapi/storage/accounts/sqlite3/deltas/20200929203058_is_active.go @@ -10,6 +10,7 @@ import ( func LoadFromGoose() { goose.AddMigration(UpIsActive, DownIsActive) + goose.AddMigration(UpAddPolicyVersion, DownAddPolicyVersion) } func LoadIsActive(m *sqlutil.Migrations) { diff --git a/userapi/storage/accounts/sqlite3/deltas/2022021414375800_add_policy_version.go b/userapi/storage/accounts/sqlite3/deltas/2022021414375800_add_policy_version.go new file mode 100644 index 0000000000..ae69cf0f01 --- /dev/null +++ b/userapi/storage/accounts/sqlite3/deltas/2022021414375800_add_policy_version.go @@ -0,0 +1,28 @@ +package deltas + +import ( + "database/sql" + "fmt" + + "github.com/matrix-org/dendrite/internal/sqlutil" +) + +func LoadAddPolicyVersion(m *sqlutil.Migrations) { + m.AddMigration(UpAddPolicyVersion, DownAddPolicyVersion) +} + +func UpAddPolicyVersion(tx *sql.Tx) error { + _, err := tx.Exec("ALTER TABLE account_accounts ADD COLUMN policy_version TEXT;") + if err != nil { + return fmt.Errorf("failed to execute upgrade: %w", err) + } + return nil +} + +func DownAddPolicyVersion(tx *sql.Tx) error { + _, err := tx.Exec("ALTER TABLE account_accounts DROP COLUMN policy_version;") + if err != nil { + return fmt.Errorf("failed to execute downgrade: %w", err) + } + return nil +} diff --git a/userapi/storage/accounts/sqlite3/storage.go b/userapi/storage/accounts/sqlite3/storage.go index 911492d539..c7551299b1 100644 --- a/userapi/storage/accounts/sqlite3/storage.go +++ b/userapi/storage/accounts/sqlite3/storage.go @@ -77,6 +77,7 @@ func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserver } m := sqlutil.NewMigrations() deltas.LoadIsActive(m) + deltas.LoadAddPolicyVersion(m) if err = m.RunDeltas(db, dbProperties); err != nil { return nil, err } From 097f1d46098cda7089c9f16ae73c765f8f6f4284 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 14 Feb 2022 15:08:00 +0100 Subject: [PATCH 09/56] Add a way to update the policy_version for a user --- userapi/api/api.go | 11 +++++- userapi/api/api_trace.go | 8 ++++- userapi/internal/api.go | 8 +++++ userapi/inthttp/client.go | 35 ++++++++++++------- userapi/inthttp/server.go | 18 ++++++++-- userapi/storage/accounts/interface.go | 1 + .../accounts/postgres/accounts_table.go | 17 +++++++++ userapi/storage/accounts/postgres/storage.go | 10 +++++- .../accounts/sqlite3/accounts_table.go | 17 +++++++++ userapi/storage/accounts/sqlite3/storage.go | 8 +++++ 10 files changed, 115 insertions(+), 18 deletions(-) diff --git a/userapi/api/api.go b/userapi/api/api.go index e5a173cec6..f38180a63f 100644 --- a/userapi/api/api.go +++ b/userapi/api/api.go @@ -46,6 +46,7 @@ type UserInternalAPI interface { QueryOpenIDToken(ctx context.Context, req *QueryOpenIDTokenRequest, res *QueryOpenIDTokenResponse) error QueryPolicyVersion(ctx context.Context, req *QueryPolicyVersionRequest, res *QueryPolicyVersionResponse) error GetOutdatedPolicy(ctx context.Context, req *QueryOutdatedPolicyUsersRequest, res *QueryOutdatedPolicyUsersResponse) error + PerformUpdatePolicyVersion(ctx context.Context, req *UpdatePolicyVersionRequest, res *UpdatePolicyVersionResponse) error } type PerformKeyBackupRequest struct { @@ -347,7 +348,7 @@ type QueryPolicyVersionResponse struct { PolicyVersion string } -// QueryOutdatedPolicyUsersRequest is the response for QueryOutdatedPolicyUsersRequest +// QueryOutdatedPolicyUsersRequest is the request for QueryOutdatedPolicyUsersRequest type QueryOutdatedPolicyUsersRequest struct { PolicyVersion string } @@ -357,6 +358,14 @@ type QueryOutdatedPolicyUsersResponse struct { OutdatedUsers []string } +// UpdatePolicyVersionRequest is the request for UpdatePolicyVersionRequest +type UpdatePolicyVersionRequest struct { + PolicyVersion, LocalPart string +} + +// UpdatePolicyVersionResponse is the response for UpdatePolicyVersionRequest +type UpdatePolicyVersionResponse struct{} + // Device represents a client's device (mobile, web, etc) type Device struct { ID string diff --git a/userapi/api/api_trace.go b/userapi/api/api_trace.go index 15b5572f4b..3428e0fd36 100644 --- a/userapi/api/api_trace.go +++ b/userapi/api/api_trace.go @@ -127,7 +127,13 @@ func (t *UserInternalAPITrace) QueryPolicyVersion(ctx context.Context, req *Quer func (t *UserInternalAPITrace) GetOutdatedPolicy(ctx context.Context, req *QueryOutdatedPolicyUsersRequest, res *QueryOutdatedPolicyUsersResponse) error { err := t.Impl.GetOutdatedPolicy(ctx, req, res) - util.GetLogger(ctx).Infof("QueryPolicyVersion req=%+v res=%+v", js(req), js(res)) + util.GetLogger(ctx).Infof("GetOutdatedPolicy req=%+v res=%+v", js(req), js(res)) + return err +} + +func (t *UserInternalAPITrace) PerformUpdatePolicyVersion(ctx context.Context, req *UpdatePolicyVersionRequest, res *UpdatePolicyVersionResponse) error { + err := t.Impl.PerformUpdatePolicyVersion(ctx, req, res) + util.GetLogger(ctx).Infof("PerformUpdatePolicyVersion req=%+v res=%+v", js(req), js(res)) return err } diff --git a/userapi/internal/api.go b/userapi/internal/api.go index 0d7a723a97..a6b933e3f1 100644 --- a/userapi/internal/api.go +++ b/userapi/internal/api.go @@ -615,3 +615,11 @@ func (a *UserInternalAPI) GetOutdatedPolicy( return nil } + +func (a *UserInternalAPI) PerformUpdatePolicyVersion( + ctx context.Context, + req *api.UpdatePolicyVersionRequest, + res *api.UpdatePolicyVersionResponse, +) error { + return a.AccountDB.UpdatePolicyVersion(ctx, req.PolicyVersion, req.LocalPart) +} diff --git a/userapi/inthttp/client.go b/userapi/inthttp/client.go index 4a99d1b13c..9067d993ed 100644 --- a/userapi/inthttp/client.go +++ b/userapi/inthttp/client.go @@ -37,17 +37,18 @@ const ( PerformAccountDeactivationPath = "/userapi/performAccountDeactivation" PerformOpenIDTokenCreationPath = "/userapi/performOpenIDTokenCreation" PerformKeyBackupPath = "/userapi/performKeyBackup" - - QueryKeyBackupPath = "/userapi/queryKeyBackup" - QueryProfilePath = "/userapi/queryProfile" - QueryAccessTokenPath = "/userapi/queryAccessToken" - QueryDevicesPath = "/userapi/queryDevices" - QueryAccountDataPath = "/userapi/queryAccountData" - QueryDeviceInfosPath = "/userapi/queryDeviceInfos" - QuerySearchProfilesPath = "/userapi/querySearchProfiles" - QueryOpenIDTokenPath = "/userapi/queryOpenIDToken" - QueryPolicyVersion = "/userapi/queryPolicyVersion" - QueryOutdatedPolicyUsers = "/userapi/queryOutdatedPolicy" + PerformUpdatePolicyVersionPath = "/userapi/performUpdatePolicyVersion" + + QueryKeyBackupPath = "/userapi/queryKeyBackup" + QueryProfilePath = "/userapi/queryProfile" + QueryAccessTokenPath = "/userapi/queryAccessToken" + QueryDevicesPath = "/userapi/queryDevices" + QueryAccountDataPath = "/userapi/queryAccountData" + QueryDeviceInfosPath = "/userapi/queryDeviceInfos" + QuerySearchProfilesPath = "/userapi/querySearchProfiles" + QueryOpenIDTokenPath = "/userapi/queryOpenIDToken" + QueryPolicyVersionPath = "/userapi/queryPolicyVersion" + QueryOutdatedPolicyUsersPath = "/userapi/queryOutdatedPolicy" ) // NewUserAPIClient creates a UserInternalAPI implemented by talking to a HTTP POST API. @@ -256,7 +257,7 @@ func (h *httpUserInternalAPI) QueryPolicyVersion(ctx context.Context, req *api.Q span, ctx := opentracing.StartSpanFromContext(ctx, "QueryKeyBackup") defer span.Finish() - apiURL := h.apiURL + QueryPolicyVersion + apiURL := h.apiURL + QueryPolicyVersionPath return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) } @@ -264,6 +265,14 @@ func (h *httpUserInternalAPI) GetOutdatedPolicy(ctx context.Context, req *api.Qu span, ctx := opentracing.StartSpanFromContext(ctx, "QueryKeyBackup") defer span.Finish() - apiURL := h.apiURL + QueryOutdatedPolicyUsers + apiURL := h.apiURL + QueryOutdatedPolicyUsersPath + return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) +} + +func (h *httpUserInternalAPI) PerformUpdatePolicyVersion(ctx context.Context, req *api.UpdatePolicyVersionRequest, res *api.UpdatePolicyVersionResponse) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "QueryKeyBackup") + defer span.Finish() + + apiURL := h.apiURL + PerformUpdatePolicyVersionPath return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) } diff --git a/userapi/inthttp/server.go b/userapi/inthttp/server.go index 22e49181ce..70e8d17ceb 100644 --- a/userapi/inthttp/server.go +++ b/userapi/inthttp/server.go @@ -265,7 +265,7 @@ func AddRoutes(internalAPIMux *mux.Router, s api.UserInternalAPI) { return util.JSONResponse{Code: http.StatusOK, JSON: &response} }), ) - internalAPIMux.Handle(QueryPolicyVersion, + internalAPIMux.Handle(QueryPolicyVersionPath, httputil.MakeInternalAPI("queryPolicyVersion", func(req *http.Request) util.JSONResponse { request := api.QueryPolicyVersionRequest{} response := api.QueryPolicyVersionResponse{} @@ -279,7 +279,7 @@ func AddRoutes(internalAPIMux *mux.Router, s api.UserInternalAPI) { return util.JSONResponse{Code: http.StatusOK, JSON: &response} }), ) - internalAPIMux.Handle(QueryOutdatedPolicyUsers, + internalAPIMux.Handle(QueryOutdatedPolicyUsersPath, httputil.MakeInternalAPI("queryOutdatedPolicyUsers", func(req *http.Request) util.JSONResponse { request := api.QueryOutdatedPolicyUsersRequest{} response := api.QueryOutdatedPolicyUsersResponse{} @@ -293,4 +293,18 @@ func AddRoutes(internalAPIMux *mux.Router, s api.UserInternalAPI) { return util.JSONResponse{Code: http.StatusOK, JSON: &response} }), ) + internalAPIMux.Handle(PerformUpdatePolicyVersionPath, + httputil.MakeInternalAPI("performUpdatePolicyVersionPath", func(req *http.Request) util.JSONResponse { + request := api.UpdatePolicyVersionRequest{} + response := api.UpdatePolicyVersionResponse{} + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + err := s.PerformUpdatePolicyVersion(req.Context(), &request, &response) + if err != nil { + return util.JSONResponse{Code: http.StatusBadRequest, JSON: &response} + } + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) } diff --git a/userapi/storage/accounts/interface.go b/userapi/storage/accounts/interface.go index e172c09515..61f59c9b98 100644 --- a/userapi/storage/accounts/interface.go +++ b/userapi/storage/accounts/interface.go @@ -54,6 +54,7 @@ type Database interface { GetOpenIDTokenAttributes(ctx context.Context, token string) (*api.OpenIDTokenAttributes, error) GetPrivacyPolicy(ctx context.Context, localpart string) (policyVersion string, err error) GetOutdatedPolicy(ctx context.Context, policyVersion string) (userIDs []string, err error) + UpdatePolicyVersion(ctx context.Context, policyVersion, localpart string) error // Key backups CreateKeyBackup(ctx context.Context, userID, algorithm string, authData json.RawMessage) (version string, err error) diff --git a/userapi/storage/accounts/postgres/accounts_table.go b/userapi/storage/accounts/postgres/accounts_table.go index db8cdecdf0..1b22a44e4c 100644 --- a/userapi/storage/accounts/postgres/accounts_table.go +++ b/userapi/storage/accounts/postgres/accounts_table.go @@ -73,6 +73,9 @@ const selectPrivacyPolicySQL = "" + const batchSelectPrivacyPolicySQL = "" + "SELECT localpart FROM account_accounts WHERE policy_version IS NULL or policy_version <> $1" +const updatePolicyVersionSQL = "" + + "UPDATE account_accounts SET policy_version = $1 WHERE localpart = $2" + type accountsStatements struct { insertAccountStmt *sql.Stmt updatePasswordStmt *sql.Stmt @@ -82,6 +85,7 @@ type accountsStatements struct { selectNewNumericLocalpartStmt *sql.Stmt selectPrivacyPolicyStmt *sql.Stmt batchSelectPrivacyPolicyStmt *sql.Stmt + updatePolicyVersionStmt *sql.Stmt serverName gomatrixserverlib.ServerName } @@ -101,6 +105,7 @@ func (s *accountsStatements) prepare(db *sql.DB, server gomatrixserverlib.Server {&s.selectNewNumericLocalpartStmt, selectNewNumericLocalpartSQL}, {&s.selectPrivacyPolicyStmt, selectPrivacyPolicySQL}, {&s.batchSelectPrivacyPolicyStmt, batchSelectPrivacyPolicySQL}, + {&s.updatePolicyVersionStmt, updatePolicyVersionSQL}, }.Prepare(db) } @@ -218,3 +223,15 @@ func (s *accountsStatements) batchSelectPrivacyPolicy( } return userIDs, rows.Err() } + +// updatePolicyVersion sets the policy_version for a specific user +func (s *accountsStatements) updatePolicyVersion( + ctx context.Context, txn *sql.Tx, policyVersion, localpart string, +) (err error) { + stmt := s.updatePolicyVersionStmt + if txn != nil { + stmt = sqlutil.TxStmt(txn, stmt) + } + _, err = stmt.ExecContext(ctx, policyVersion, localpart) + return err +} diff --git a/userapi/storage/accounts/postgres/storage.go b/userapi/storage/accounts/postgres/storage.go index ff91ae4473..4e5aa20a79 100644 --- a/userapi/storage/accounts/postgres/storage.go +++ b/userapi/storage/accounts/postgres/storage.go @@ -529,7 +529,7 @@ func (d *Database) GetPrivacyPolicy(ctx context.Context, localpart string) (poli return } -// GetOutdatedPolicy queries all users which didn't accept the current policy version +// GetOutdatedPolicy queries all users which didn't accept the current policy version. func (d *Database) GetOutdatedPolicy(ctx context.Context, policyVersion string) (userIDs []string, err error) { err = d.writer.Do(d.db, nil, func(txn *sql.Tx) error { userIDs, err = d.accounts.batchSelectPrivacyPolicy(ctx, txn, policyVersion) @@ -537,3 +537,11 @@ func (d *Database) GetOutdatedPolicy(ctx context.Context, policyVersion string) }) return } + +// UpdatePolicyVersion sets the accepted policy_version for a user. +func (d *Database) UpdatePolicyVersion(ctx context.Context, policyVersion, localpart string) (err error) { + err = d.writer.Do(d.db, nil, func(txn *sql.Tx) error { + return d.accounts.updatePolicyVersion(ctx, txn, policyVersion, localpart) + }) + return +} diff --git a/userapi/storage/accounts/sqlite3/accounts_table.go b/userapi/storage/accounts/sqlite3/accounts_table.go index 95b9eb7bf3..f64c05717a 100644 --- a/userapi/storage/accounts/sqlite3/accounts_table.go +++ b/userapi/storage/accounts/sqlite3/accounts_table.go @@ -71,6 +71,9 @@ const selectPrivacyPolicySQL = "" + const batchSelectPrivacyPolicySQL = "" + "SELECT localpart FROM account_accounts WHERE policy_version IS NULL or policy_version <> $1" +const updatePolicyVersionSQL = "" + + "UPDATE account_accounts SET policy_version = $1 WHERE localpart = $2" + type accountsStatements struct { db *sql.DB insertAccountStmt *sql.Stmt @@ -81,6 +84,7 @@ type accountsStatements struct { selectNewNumericLocalpartStmt *sql.Stmt selectPrivacyPolicyStmt *sql.Stmt batchSelectPrivacyPolicyStmt *sql.Stmt + updatePolicyVersionStmt *sql.Stmt serverName gomatrixserverlib.ServerName } @@ -101,6 +105,7 @@ func (s *accountsStatements) prepare(db *sql.DB, server gomatrixserverlib.Server {&s.selectNewNumericLocalpartStmt, selectNewNumericLocalpartSQL}, {&s.selectPrivacyPolicyStmt, selectPrivacyPolicySQL}, {&s.batchSelectPrivacyPolicyStmt, batchSelectPrivacyPolicySQL}, + {&s.updatePolicyVersionStmt, updatePolicyVersionSQL}, }.Prepare(db) } @@ -219,3 +224,15 @@ func (s *accountsStatements) batchSelectPrivacyPolicy( } return userIDs, rows.Err() } + +// updatePolicyVersion sets the policy_version for a specific user +func (s *accountsStatements) updatePolicyVersion( + ctx context.Context, txn *sql.Tx, policyVersion, localpart string, +) (err error) { + stmt := s.updatePolicyVersionStmt + if txn != nil { + stmt = sqlutil.TxStmt(txn, stmt) + } + _, err = stmt.ExecContext(ctx, policyVersion, localpart) + return err +} diff --git a/userapi/storage/accounts/sqlite3/storage.go b/userapi/storage/accounts/sqlite3/storage.go index c7551299b1..768d58f658 100644 --- a/userapi/storage/accounts/sqlite3/storage.go +++ b/userapi/storage/accounts/sqlite3/storage.go @@ -580,3 +580,11 @@ func (d *Database) GetOutdatedPolicy(ctx context.Context, policyVersion string) }) return } + +// UpdatePolicyVersion sets the accepted policy_version for a user. +func (d *Database) UpdatePolicyVersion(ctx context.Context, policyVersion, localpart string) (err error) { + err = d.writer.Do(d.db, nil, func(txn *sql.Tx) error { + return d.accounts.updatePolicyVersion(ctx, txn, policyVersion, localpart) + }) + return +} From b2045c24cb3806a139a3f0efef37592359c7a4e5 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 14 Feb 2022 16:18:19 +0100 Subject: [PATCH 10/56] Add missing yaml tag --- setup/config/config_global.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/config/config_global.go b/setup/config/config_global.go index ad69a6d5e8..4ef8430444 100644 --- a/setup/config/config_global.go +++ b/setup/config/config_global.go @@ -209,7 +209,7 @@ func (c *DNSCacheOptions) Verify(configErrs *ConfigErrors, isMonolith bool) { // messages will be sent to the users. type UserConsentOptions struct { // Randomly generated string to be used to calculate the HMAC - FormSecret string + FormSecret string `yaml:"form_secret"` // Require consent when user registers for the first time RequireAtRegistration bool `yaml:"require_at_registration"` // The name to be shown to the user From 11144de92f01da5f307eb261fdb85faab6a7d755 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 14 Feb 2022 16:18:51 +0100 Subject: [PATCH 11/56] Implement consent tracking --- clientapi/routing/consent_tracking.go | 86 ++++++++++++++++++++-- clientapi/routing/consent_tracking_test.go | 48 ++++++++++++ 2 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 clientapi/routing/consent_tracking_test.go diff --git a/clientapi/routing/consent_tracking.go b/clientapi/routing/consent_tracking.go index 5947855d4c..d3c1e3947e 100644 --- a/clientapi/routing/consent_tracking.go +++ b/clientapi/routing/consent_tracking.go @@ -1,10 +1,14 @@ package routing import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" "net/http" "github.com/matrix-org/dendrite/setup/config" userapi "github.com/matrix-org/dendrite/userapi/api" + "github.com/matrix-org/gomatrixserverlib" "github.com/sirupsen/logrus" ) @@ -25,23 +29,93 @@ func consent(userAPI userapi.UserInternalAPI, cfg *config.ClientAPI) http.Handle _, _ = writer.Write([]byte("consent tracking is disabled")) return } + // The data used to populate the /consent request + data := constentTemplateData{ + User: req.FormValue("u"), + Version: req.FormValue("v"), + UserHMAC: req.FormValue("h"), + } switch req.Method { case http.MethodGet: - // The data used to populate the /consent request - data := constentTemplateData{ - User: req.FormValue("u"), - Version: req.FormValue("v"), - UserHMAC: req.FormValue("h"), - } // display the privacy policy without a form data.PublicVersion = data.User == "" || data.UserHMAC == "" || data.Version == "" + // let's see if the user already consented to the current version + if !data.PublicVersion { + res := &userapi.QueryPolicyVersionResponse{} + localPart, _, err := gomatrixserverlib.SplitID('@', data.User) + if err != nil { + logrus.WithError(err).Error("unable to print consent template") + return + } + if err = userAPI.QueryPolicyVersion(req.Context(), &userapi.QueryPolicyVersionRequest{ + LocalPart: localPart, + }, res); err != nil { + logrus.WithError(err).Error("unable to print consent template") + return + } + data.HasConsented = res.PolicyVersion == consentCfg.Version + } + err := consentCfg.Templates.ExecuteTemplate(writer, consentCfg.Version+".gohtml", data) if err != nil { logrus.WithError(err).Error("unable to print consent template") + return } case http.MethodPost: + localPart, _, err := gomatrixserverlib.SplitID('@', data.User) + if err != nil { + logrus.WithError(err).Error("unable to split username") + return + } + ok, err := validHMAC(data.User, data.UserHMAC, consentCfg.FormSecret) + if err != nil || !ok { + writer.WriteHeader(http.StatusBadRequest) + _, err = writer.Write([]byte("invalid HMAC provided")) + if err != nil { + return + } + return + } + if err := userAPI.PerformUpdatePolicyVersion( + req.Context(), + &userapi.UpdatePolicyVersionRequest{ + PolicyVersion: data.Version, + LocalPart: localPart, + }, + &userapi.UpdatePolicyVersionResponse{}, + ); err != nil { + writer.WriteHeader(http.StatusInternalServerError) + _, err = writer.Write([]byte("unable to update database")) + if err != nil { + logrus.WithError(err).Error("unable to write to database") + } + return + } + // display the privacy policy without a form + data.PublicVersion = false + data.HasConsented = true + + err = consentCfg.Templates.ExecuteTemplate(writer, consentCfg.Version+".gohtml", data) + if err != nil { + logrus.WithError(err).Error("unable to print consent template") + return + } } } } + +func validHMAC(username, userHMAC, secret string) (bool, error) { + mac := hmac.New(sha256.New, []byte(secret)) + _, err := mac.Write([]byte(username)) + if err != nil { + return false, err + } + expectedMAC := mac.Sum(nil) + decoded, err := hex.DecodeString(userHMAC) + if err != nil { + return false, err + } + return hmac.Equal(decoded, expectedMAC), nil +} diff --git a/clientapi/routing/consent_tracking_test.go b/clientapi/routing/consent_tracking_test.go new file mode 100644 index 0000000000..3ddcad778e --- /dev/null +++ b/clientapi/routing/consent_tracking_test.go @@ -0,0 +1,48 @@ +package routing + +import "testing" + +func Test_validHMAC(t *testing.T) { + type args struct { + username string + userHMAC string + secret string + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + { + name: "invalid hmac", + args: args{}, + wantErr: false, + want: false, + }, + // $ echo -n '@alice:localhost' | openssl sha256 -hmac 'helloWorld' 27m ⚑ ◒ 15:35:54 + //(stdin)= 121c9bab767ed87a3136db0c3002144dfe414720aa328d235199082e4757541e + // + { + name: "valid hmac", + args: args{ + username: "@alice:localhost", + userHMAC: "121c9bab767ed87a3136db0c3002144dfe414720aa328d235199082e4757541e", + secret: "helloWorld", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := validHMAC(tt.args.username, tt.args.userHMAC, tt.args.secret) + if (err != nil) != tt.wantErr { + t.Errorf("validHMAC() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("validHMAC() got = %v, want %v", got, tt.want) + } + }) + } +} From 89340cfc524a12dd094b3e4deabfc109c95ebce8 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 14 Feb 2022 18:11:56 +0100 Subject: [PATCH 12/56] Verify the user has given their consent, otherwise block access --- clientapi/routing/routing.go | 158 +++++++++++++++++----------------- internal/httputil/httpapi.go | 30 ++++++- mediaapi/routing/routing.go | 17 ++-- setup/mscs/msc2836/msc2836.go | 2 +- setup/mscs/msc2946/msc2946.go | 2 +- syncapi/routing/routing.go | 10 +-- 6 files changed, 123 insertions(+), 96 deletions(-) diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index d10817d1b8..467c6e78e4 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -118,18 +118,20 @@ func Setup( } // unspecced consent tracking - consentAPIMux.HandleFunc("/consent", consent(userAPI, cfg)).Methods(http.MethodGet, http.MethodPost, http.MethodOptions) + if cfg.Matrix.UserConsentOptions.Enabled() { + consentAPIMux.HandleFunc("/consent", consent(userAPI, cfg)).Methods(http.MethodGet, http.MethodPost, http.MethodOptions) + } r0mux := publicAPIMux.PathPrefix("/r0").Subrouter() unstableMux := publicAPIMux.PathPrefix("/unstable").Subrouter() r0mux.Handle("/createRoom", - httputil.MakeAuthAPI("createRoom", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("createRoom", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return CreateRoom(req, device, cfg, accountDB, rsAPI, asAPI) }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/join/{roomIDOrAlias}", - httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -145,7 +147,7 @@ func Setup( if mscCfg.Enabled("msc2753") { r0mux.Handle("/peek/{roomIDOrAlias}", - httputil.MakeAuthAPI(gomatrixserverlib.Peek, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI(gomatrixserverlib.Peek, userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -160,12 +162,12 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) } r0mux.Handle("/joined_rooms", - httputil.MakeAuthAPI("joined_rooms", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("joined_rooms", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetJoinedRooms(req, device, rsAPI) }), ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/join", - httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -179,7 +181,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/leave", - httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -193,7 +195,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/unpeek", - httputil.MakeAuthAPI("unpeek", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("unpeek", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -204,7 +206,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/ban", - httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -213,7 +215,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/invite", - httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -225,7 +227,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/kick", - httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -234,7 +236,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/unban", - httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -243,7 +245,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/send/{eventType}", - httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -252,7 +254,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/send/{eventType}/{txnID}", - httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -263,7 +265,7 @@ func Setup( }), ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/event/{eventID}", - httputil.MakeAuthAPI("rooms_get_event", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_get_event", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -272,7 +274,7 @@ func Setup( }), ).Methods(http.MethodGet, http.MethodOptions) - r0mux.Handle("/rooms/{roomID}/state", httputil.MakeAuthAPI("room_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + r0mux.Handle("/rooms/{roomID}/state", httputil.MakeAuthAPI("room_state", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -280,7 +282,7 @@ func Setup( return OnIncomingStateRequest(req.Context(), device, rsAPI, vars["roomID"]) })).Methods(http.MethodGet, http.MethodOptions) - r0mux.Handle("/rooms/{roomID}/aliases", httputil.MakeAuthAPI("aliases", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + r0mux.Handle("/rooms/{roomID}/aliases", httputil.MakeAuthAPI("aliases", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -288,7 +290,7 @@ func Setup( return GetAliases(req, rsAPI, device, vars["roomID"]) })).Methods(http.MethodGet, http.MethodOptions) - r0mux.Handle("/rooms/{roomID}/state/{type:[^/]+/?}", httputil.MakeAuthAPI("room_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + r0mux.Handle("/rooms/{roomID}/state/{type:[^/]+/?}", httputil.MakeAuthAPI("room_state", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -299,7 +301,7 @@ func Setup( return OnIncomingStateTypeRequest(req.Context(), device, rsAPI, vars["roomID"], eventType, "", eventFormat) })).Methods(http.MethodGet, http.MethodOptions) - r0mux.Handle("/rooms/{roomID}/state/{type}/{stateKey}", httputil.MakeAuthAPI("room_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + r0mux.Handle("/rooms/{roomID}/state/{type}/{stateKey}", httputil.MakeAuthAPI("room_state", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -309,7 +311,7 @@ func Setup( })).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/state/{eventType:[^/]+/?}", - httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -321,7 +323,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/state/{eventType}/{stateKey}", - httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -356,7 +358,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/directory/room/{roomAlias}", - httputil.MakeAuthAPI("directory_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("directory_room", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -366,7 +368,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/directory/room/{roomAlias}", - httputil.MakeAuthAPI("directory_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("directory_room", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -385,7 +387,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) // TODO: Add AS support r0mux.Handle("/directory/list/room/{roomID}", - httputil.MakeAuthAPI("directory_list", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("directory_list", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -400,19 +402,19 @@ func Setup( ).Methods(http.MethodGet, http.MethodPost, http.MethodOptions) r0mux.Handle("/logout", - httputil.MakeAuthAPI("logout", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("logout", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return Logout(req, userAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/logout/all", - httputil.MakeAuthAPI("logout", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("logout", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return LogoutAll(req, userAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/typing/{userID}", - httputil.MakeAuthAPI("rooms_typing", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_typing", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -424,7 +426,7 @@ func Setup( }), ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/redact/{eventID}", - httputil.MakeAuthAPI("rooms_redact", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_redact", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -433,7 +435,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/redact/{eventID}/{txnId}", - httputil.MakeAuthAPI("rooms_redact", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_redact", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -443,7 +445,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/sendToDevice/{eventType}/{txnID}", - httputil.MakeAuthAPI("send_to_device", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_to_device", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -457,7 +459,7 @@ func Setup( // rather than r0. It's an exact duplicate of the above handler. // TODO: Remove this if/when sytest is fixed! unstableMux.Handle("/sendToDevice/{eventType}/{txnID}", - httputil.MakeAuthAPI("send_to_device", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_to_device", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -468,7 +470,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/account/whoami", - httputil.MakeAuthAPI("whoami", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("whoami", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -477,7 +479,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/account/password", - httputil.MakeAuthAPI("password", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("password", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -486,7 +488,7 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/account/deactivate", - httputil.MakeAuthAPI("deactivate", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("deactivate", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -554,7 +556,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/profile/{userID}/avatar_url", - httputil.MakeAuthAPI("profile_avatar_url", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("profile_avatar_url", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -579,7 +581,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/profile/{userID}/displayname", - httputil.MakeAuthAPI("profile_displayname", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("profile_displayname", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -594,19 +596,19 @@ func Setup( // PUT requests, so we need to allow this method r0mux.Handle("/account/3pid", - httputil.MakeAuthAPI("account_3pid", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("account_3pid", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetAssociated3PIDs(req, accountDB, device) }), ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/account/3pid", - httputil.MakeAuthAPI("account_3pid", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("account_3pid", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return CheckAndSave3PIDAssociation(req, accountDB, device, cfg) }), ).Methods(http.MethodPost, http.MethodOptions) unstableMux.Handle("/account/3pid/delete", - httputil.MakeAuthAPI("account_3pid", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("account_3pid", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return Forget3PID(req, accountDB) }), ).Methods(http.MethodPost, http.MethodOptions) @@ -632,7 +634,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/voip/turnServer", - httputil.MakeAuthAPI("turn_server", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("turn_server", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -661,7 +663,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/user/{userID}/account_data/{type}", - httputil.MakeAuthAPI("user_account_data", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -671,7 +673,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/user/{userID}/rooms/{roomID}/account_data/{type}", - httputil.MakeAuthAPI("user_account_data", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -681,7 +683,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/user/{userID}/account_data/{type}", - httputil.MakeAuthAPI("user_account_data", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -691,7 +693,7 @@ func Setup( ).Methods(http.MethodGet) r0mux.Handle("/user/{userID}/rooms/{roomID}/account_data/{type}", - httputil.MakeAuthAPI("user_account_data", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -701,7 +703,7 @@ func Setup( ).Methods(http.MethodGet) r0mux.Handle("/admin/whois/{userID}", - httputil.MakeAuthAPI("admin_whois", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("admin_whois", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -711,7 +713,7 @@ func Setup( ).Methods(http.MethodGet) r0mux.Handle("/user/{userID}/openid/request_token", - httputil.MakeAuthAPI("openid_request_token", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("openid_request_token", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -724,7 +726,7 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/user_directory/search", - httputil.MakeAuthAPI("userdirectory_search", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("userdirectory_search", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -749,7 +751,7 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/members", - httputil.MakeAuthAPI("rooms_members", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_members", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -759,7 +761,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/joined_members", - httputil.MakeAuthAPI("rooms_members", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_members", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -769,7 +771,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/read_markers", - httputil.MakeAuthAPI("rooms_read_markers", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_read_markers", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -782,7 +784,7 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/forget", - httputil.MakeAuthAPI("rooms_forget", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_forget", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -795,13 +797,13 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/devices", - httputil.MakeAuthAPI("get_devices", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_devices", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetDevicesByLocalpart(req, userAPI, device) }), ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/devices/{deviceID}", - httputil.MakeAuthAPI("get_device", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_device", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -811,7 +813,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/devices/{deviceID}", - httputil.MakeAuthAPI("device_data", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("device_data", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -821,7 +823,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/devices/{deviceID}", - httputil.MakeAuthAPI("delete_device", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("delete_device", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -831,7 +833,7 @@ func Setup( ).Methods(http.MethodDelete, http.MethodOptions) r0mux.Handle("/delete_devices", - httputil.MakeAuthAPI("delete_devices", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("delete_devices", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return DeleteDevices(req, userAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) @@ -856,7 +858,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/user/{userId}/rooms/{roomId}/tags", - httputil.MakeAuthAPI("get_tags", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_tags", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -866,7 +868,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/user/{userId}/rooms/{roomId}/tags/{tag}", - httputil.MakeAuthAPI("put_tag", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("put_tag", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -876,7 +878,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/user/{userId}/rooms/{roomId}/tags/{tag}", - httputil.MakeAuthAPI("delete_tag", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("delete_tag", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -886,7 +888,7 @@ func Setup( ).Methods(http.MethodDelete, http.MethodOptions) r0mux.Handle("/capabilities", - httputil.MakeAuthAPI("capabilities", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("capabilities", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -896,7 +898,7 @@ func Setup( // Key Backup Versions (Metadata) - getBackupKeysVersion := httputil.MakeAuthAPI("get_backup_keys_version", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getBackupKeysVersion := httputil.MakeAuthAPI("get_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -904,11 +906,11 @@ func Setup( return KeyBackupVersion(req, userAPI, device, vars["version"]) }) - getLatestBackupKeysVersion := httputil.MakeAuthAPI("get_latest_backup_keys_version", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getLatestBackupKeysVersion := httputil.MakeAuthAPI("get_latest_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return KeyBackupVersion(req, userAPI, device, "") }) - putBackupKeysVersion := httputil.MakeAuthAPI("put_backup_keys_version", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + putBackupKeysVersion := httputil.MakeAuthAPI("put_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -916,7 +918,7 @@ func Setup( return ModifyKeyBackupVersionAuthData(req, userAPI, device, vars["version"]) }) - deleteBackupKeysVersion := httputil.MakeAuthAPI("delete_backup_keys_version", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + deleteBackupKeysVersion := httputil.MakeAuthAPI("delete_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -924,7 +926,7 @@ func Setup( return DeleteKeyBackupVersion(req, userAPI, device, vars["version"]) }) - postNewBackupKeysVersion := httputil.MakeAuthAPI("post_new_backup_keys_version", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + postNewBackupKeysVersion := httputil.MakeAuthAPI("post_new_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return CreateKeyBackupVersion(req, userAPI, device) }) @@ -943,7 +945,7 @@ func Setup( // Inserting E2E Backup Keys // Bulk room and session - putBackupKeys := httputil.MakeAuthAPI("put_backup_keys", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + putBackupKeys := httputil.MakeAuthAPI("put_backup_keys", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { version := req.URL.Query().Get("version") if version == "" { return util.JSONResponse{ @@ -960,7 +962,7 @@ func Setup( }) // Single room bulk session - putBackupKeysRoom := httputil.MakeAuthAPI("put_backup_keys_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + putBackupKeysRoom := httputil.MakeAuthAPI("put_backup_keys_room", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -992,7 +994,7 @@ func Setup( }) // Single room, single session - putBackupKeysRoomSession := httputil.MakeAuthAPI("put_backup_keys_room_session", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + putBackupKeysRoomSession := httputil.MakeAuthAPI("put_backup_keys_room_session", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -1034,11 +1036,11 @@ func Setup( // Querying E2E Backup Keys - getBackupKeys := httputil.MakeAuthAPI("get_backup_keys", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getBackupKeys := httputil.MakeAuthAPI("get_backup_keys", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetBackupKeys(req, userAPI, device, req.URL.Query().Get("version"), "", "") }) - getBackupKeysRoom := httputil.MakeAuthAPI("get_backup_keys_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getBackupKeysRoom := httputil.MakeAuthAPI("get_backup_keys_room", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -1046,7 +1048,7 @@ func Setup( return GetBackupKeys(req, userAPI, device, req.URL.Query().Get("version"), vars["roomID"], "") }) - getBackupKeysRoomSession := httputil.MakeAuthAPI("get_backup_keys_room_session", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getBackupKeysRoomSession := httputil.MakeAuthAPI("get_backup_keys_room_session", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -1066,11 +1068,11 @@ func Setup( // Cross-signing device keys - postDeviceSigningKeys := httputil.MakeAuthAPI("post_device_signing_keys", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + postDeviceSigningKeys := httputil.MakeAuthAPI("post_device_signing_keys", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return UploadCrossSigningDeviceKeys(req, userInteractiveAuth, keyAPI, device, accountDB, cfg) }) - postDeviceSigningSignatures := httputil.MakeAuthAPI("post_device_signing_signatures", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + postDeviceSigningSignatures := httputil.MakeAuthAPI("post_device_signing_signatures", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return UploadCrossSigningDeviceSignatures(req, keyAPI, device) }) @@ -1082,27 +1084,27 @@ func Setup( // 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 { + httputil.MakeAuthAPI("keys_upload", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return UploadKeys(req, keyAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/keys/upload", - httputil.MakeAuthAPI("keys_upload", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("keys_upload", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return UploadKeys(req, keyAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/keys/query", - httputil.MakeAuthAPI("keys_query", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("keys_query", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return QueryKeys(req, keyAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/keys/claim", - httputil.MakeAuthAPI("keys_claim", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("keys_claim", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return ClaimKeys(req, keyAPI) }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomId}/receipt/{receiptType}/{eventId}", - httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } diff --git a/internal/httputil/httpapi.go b/internal/httputil/httpapi.go index 1a37a1eeca..63ba9dada4 100644 --- a/internal/httputil/httpapi.go +++ b/internal/httputil/httpapi.go @@ -29,7 +29,9 @@ import ( "github.com/getsentry/sentry-go" "github.com/gorilla/mux" "github.com/matrix-org/dendrite/clientapi/auth" + "github.com/matrix-org/dendrite/clientapi/jsonerror" federationapiAPI "github.com/matrix-org/dendrite/federationapi/api" + "github.com/matrix-org/dendrite/setup/config" userapi "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" @@ -49,7 +51,9 @@ type BasicAuth struct { // MakeAuthAPI turns a util.JSONRequestHandler function into an http.Handler which authenticates the request. func MakeAuthAPI( - metricsName string, userAPI userapi.UserInternalAPI, + metricsName string, + userAPI userapi.UserInternalAPI, + userConsentCfg config.UserConsentOptions, f func(*http.Request, *userapi.Device) util.JSONResponse, ) http.Handler { h := func(req *http.Request) util.JSONResponse { @@ -78,6 +82,30 @@ func MakeAuthAPI( } }() + // check if the user accepted any policy + if userConsentCfg.Enabled() { + localPart, _, err := gomatrixserverlib.SplitID('@', device.UserID) + if err != nil { + return jsonerror.InternalServerError() + } + // check which version of the policy the user accepted + res := &userapi.QueryPolicyVersionResponse{} + err = userAPI.QueryPolicyVersion(req.Context(), &userapi.QueryPolicyVersionRequest{ + LocalPart: localPart, + }, res) + if err != nil { + return jsonerror.InternalServerError() + } + + // user hasn't accepted any policy, block access. + if userConsentCfg.Version != res.PolicyVersion { + return util.JSONResponse{ + Code: http.StatusForbidden, + JSON: jsonerror.Forbidden(userConsentCfg.BlockEventsError), + } + } + } + jsonRes := f(req, device) // do not log 4xx as errors as they are client fails, not server fails if hub != nil && jsonRes.Code >= 500 { diff --git a/mediaapi/routing/routing.go b/mediaapi/routing/routing.go index 44f9a9d690..2476b5b205 100644 --- a/mediaapi/routing/routing.go +++ b/mediaapi/routing/routing.go @@ -60,17 +60,14 @@ func Setup( PathToResult: map[string]*types.ThumbnailGenerationResult{}, } - uploadHandler := httputil.MakeAuthAPI( - "upload", userAPI, - func(req *http.Request, dev *userapi.Device) util.JSONResponse { - if r := rateLimits.Limit(req); r != nil { - return *r - } - return Upload(req, cfg, dev, db, activeThumbnailGeneration) - }, - ) + uploadHandler := httputil.MakeAuthAPI("upload", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, dev *userapi.Device) util.JSONResponse { + if r := rateLimits.Limit(req); r != nil { + return *r + } + return Upload(req, cfg, dev, db, activeThumbnailGeneration) + }) - configHandler := httputil.MakeAuthAPI("config", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + configHandler := httputil.MakeAuthAPI("config", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } diff --git a/setup/mscs/msc2836/msc2836.go b/setup/mscs/msc2836/msc2836.go index 0af22c19a2..8679e19959 100644 --- a/setup/mscs/msc2836/msc2836.go +++ b/setup/mscs/msc2836/msc2836.go @@ -126,7 +126,7 @@ func Enable( }) base.PublicClientAPIMux.Handle("/unstable/event_relationships", - httputil.MakeAuthAPI("eventRelationships", userAPI, eventRelationshipHandler(db, rsAPI, fsAPI)), + httputil.MakeAuthAPI("eventRelationships", userAPI, base.Cfg.Global.UserConsentOptions, eventRelationshipHandler(db, rsAPI, fsAPI)), ).Methods(http.MethodPost, http.MethodOptions) base.PublicFederationAPIMux.Handle("/unstable/event_relationships", httputil.MakeExternalAPI( diff --git a/setup/mscs/msc2946/msc2946.go b/setup/mscs/msc2946/msc2946.go index 3824c99a28..5f20042d27 100644 --- a/setup/mscs/msc2946/msc2946.go +++ b/setup/mscs/msc2946/msc2946.go @@ -71,7 +71,7 @@ func Enable( }) base.PublicClientAPIMux.Handle("/unstable/org.matrix.msc2946/rooms/{roomID}/spaces", - httputil.MakeAuthAPI("spaces", userAPI, spacesHandler(db, rsAPI, fsAPI, base.Cfg.Global.ServerName)), + httputil.MakeAuthAPI("spaces", userAPI, base.Cfg.Global.UserConsentOptions, spacesHandler(db, rsAPI, fsAPI, base.Cfg.Global.ServerName)), ).Methods(http.MethodPost, http.MethodOptions) base.PublicFederationAPIMux.Handle("/unstable/org.matrix.msc2946/spaces/{roomID}", httputil.MakeExternalAPI( diff --git a/syncapi/routing/routing.go b/syncapi/routing/routing.go index e2ff27395d..a1c38300c4 100644 --- a/syncapi/routing/routing.go +++ b/syncapi/routing/routing.go @@ -42,11 +42,11 @@ func Setup( r0mux := csMux.PathPrefix("/r0").Subrouter() // TODO: Add AS support for all handlers below. - r0mux.Handle("/sync", httputil.MakeAuthAPI("sync", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + r0mux.Handle("/sync", httputil.MakeAuthAPI("sync", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return srp.OnIncomingSyncRequest(req, device) })).Methods(http.MethodGet, http.MethodOptions) - r0mux.Handle("/rooms/{roomID}/messages", httputil.MakeAuthAPI("room_messages", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + r0mux.Handle("/rooms/{roomID}/messages", httputil.MakeAuthAPI("room_messages", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -55,7 +55,7 @@ func Setup( })).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/user/{userId}/filter", - httputil.MakeAuthAPI("put_filter", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("put_filter", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -65,7 +65,7 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/user/{userId}/filter/{filterId}", - httputil.MakeAuthAPI("get_filter", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_filter", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -74,7 +74,7 @@ func Setup( }), ).Methods(http.MethodGet, http.MethodOptions) - r0mux.Handle("/keys/changes", httputil.MakeAuthAPI("keys_changes", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + r0mux.Handle("/keys/changes", httputil.MakeAuthAPI("keys_changes", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { return srp.OnIncomingKeyChangeRequest(req, device) })).Methods(http.MethodGet, http.MethodOptions) } From d19518fca5f8ae9e4950b8ac9a21b2f8ea04b93c Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Tue, 15 Feb 2022 11:07:24 +0100 Subject: [PATCH 13/56] Add ConsentNotGiven error Verify consent on desired endpoints Store consent on POST requests --- clientapi/jsonerror/jsonerror.go | 19 +++ clientapi/routing/consent_tracking.go | 138 +++++++++++----------- clientapi/routing/routing.go | 160 +++++++++++++------------- internal/httputil/httpapi.go | 77 ++++++++++--- setup/config/config_global.go | 17 ++- setup/flags.go | 3 +- setup/mscs/msc2836/msc2836.go | 2 +- setup/mscs/msc2946/msc2946.go | 2 +- syncapi/routing/routing.go | 10 +- 9 files changed, 251 insertions(+), 177 deletions(-) diff --git a/clientapi/jsonerror/jsonerror.go b/clientapi/jsonerror/jsonerror.go index caa216e62b..89c2d59b1b 100644 --- a/clientapi/jsonerror/jsonerror.go +++ b/clientapi/jsonerror/jsonerror.go @@ -29,6 +29,13 @@ type MatrixError struct { Err string `json:"error"` } +// ConsentError is an error returned to users, who didn't accept the +// TOS of this server yet. +type ConsentError struct { + MatrixError + ConsentURI string `json:"consent_uri"` +} + func (e MatrixError) Error() string { return fmt.Sprintf("%s: %s", e.ErrCode, e.Err) } @@ -193,3 +200,15 @@ func NotTrusted(serverName string) *MatrixError { Err: fmt.Sprintf("Untrusted server '%s'", serverName), } } + +// ConsentNotGiven is an error returned to users, who didn't accept the +// TOS of this server yet. +func ConsentNotGiven(consentURI string, msg string) *ConsentError { + return &ConsentError{ + MatrixError: MatrixError{ + ErrCode: "M_CONSENT_NOT_GIVEN", + Err: msg, + }, + ConsentURI: consentURI, + } +} diff --git a/clientapi/routing/consent_tracking.go b/clientapi/routing/consent_tracking.go index d3c1e3947e..2ab739067c 100644 --- a/clientapi/routing/consent_tracking.go +++ b/clientapi/routing/consent_tracking.go @@ -6,9 +6,11 @@ import ( "encoding/hex" "net/http" + "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/setup/config" userapi "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/gomatrixserverlib" + "github.com/matrix-org/util" "github.com/sirupsen/logrus" ) @@ -21,89 +23,85 @@ type constentTemplateData struct { PublicVersion bool } -func consent(userAPI userapi.UserInternalAPI, cfg *config.ClientAPI) http.HandlerFunc { +func consent(writer http.ResponseWriter, req *http.Request, userAPI userapi.UserInternalAPI, cfg *config.ClientAPI) *util.JSONResponse { consentCfg := cfg.Matrix.UserConsentOptions - return func(writer http.ResponseWriter, req *http.Request) { - if !consentCfg.Enabled() { - writer.WriteHeader(http.StatusBadRequest) - _, _ = writer.Write([]byte("consent tracking is disabled")) - return - } - // The data used to populate the /consent request - data := constentTemplateData{ - User: req.FormValue("u"), - Version: req.FormValue("v"), - UserHMAC: req.FormValue("h"), - } - switch req.Method { - case http.MethodGet: - // display the privacy policy without a form - data.PublicVersion = data.User == "" || data.UserHMAC == "" || data.Version == "" + internalError := jsonerror.InternalServerError() - // let's see if the user already consented to the current version - if !data.PublicVersion { - res := &userapi.QueryPolicyVersionResponse{} - localPart, _, err := gomatrixserverlib.SplitID('@', data.User) - if err != nil { - logrus.WithError(err).Error("unable to print consent template") - return - } - if err = userAPI.QueryPolicyVersion(req.Context(), &userapi.QueryPolicyVersionRequest{ - LocalPart: localPart, - }, res); err != nil { - logrus.WithError(err).Error("unable to print consent template") - return - } - data.HasConsented = res.PolicyVersion == consentCfg.Version - } + // The data used to populate the /consent request + data := constentTemplateData{ + User: req.FormValue("u"), + Version: req.FormValue("v"), + UserHMAC: req.FormValue("h"), + } + switch req.Method { + case http.MethodGet: + // display the privacy policy without a form + data.PublicVersion = data.User == "" || data.UserHMAC == "" || data.Version == "" - err := consentCfg.Templates.ExecuteTemplate(writer, consentCfg.Version+".gohtml", data) + // let's see if the user already consented to the current version + if !data.PublicVersion { + res := &userapi.QueryPolicyVersionResponse{} + localPart, _, err := gomatrixserverlib.SplitID('@', data.User) if err != nil { logrus.WithError(err).Error("unable to print consent template") - return + return &internalError } - case http.MethodPost: - localPart, _, err := gomatrixserverlib.SplitID('@', data.User) - if err != nil { - logrus.WithError(err).Error("unable to split username") - return + if err = userAPI.QueryPolicyVersion(req.Context(), &userapi.QueryPolicyVersionRequest{ + LocalPart: localPart, + }, res); err != nil { + logrus.WithError(err).Error("unable to print consent template") + return &internalError } + data.HasConsented = res.PolicyVersion == consentCfg.Version + } - ok, err := validHMAC(data.User, data.UserHMAC, consentCfg.FormSecret) - if err != nil || !ok { - writer.WriteHeader(http.StatusBadRequest) - _, err = writer.Write([]byte("invalid HMAC provided")) - if err != nil { - return - } - return - } - if err := userAPI.PerformUpdatePolicyVersion( - req.Context(), - &userapi.UpdatePolicyVersionRequest{ - PolicyVersion: data.Version, - LocalPart: localPart, - }, - &userapi.UpdatePolicyVersionResponse{}, - ); err != nil { - writer.WriteHeader(http.StatusInternalServerError) - _, err = writer.Write([]byte("unable to update database")) - if err != nil { - logrus.WithError(err).Error("unable to write to database") - } - return - } - // display the privacy policy without a form - data.PublicVersion = false - data.HasConsented = true + err := consentCfg.Templates.ExecuteTemplate(writer, consentCfg.Version+".gohtml", data) + if err != nil { + logrus.WithError(err).Error("unable to print consent template") + return nil + } + return nil + case http.MethodPost: + localPart, _, err := gomatrixserverlib.SplitID('@', data.User) + if err != nil { + logrus.WithError(err).Error("unable to split username") + return &internalError + } - err = consentCfg.Templates.ExecuteTemplate(writer, consentCfg.Version+".gohtml", data) + ok, err := validHMAC(data.User, data.UserHMAC, consentCfg.FormSecret) + if err != nil || !ok { + _, err = writer.Write([]byte("invalid HMAC provided")) if err != nil { - logrus.WithError(err).Error("unable to print consent template") - return + return &internalError } + return &internalError + } + if err := userAPI.PerformUpdatePolicyVersion( + req.Context(), + &userapi.UpdatePolicyVersionRequest{ + PolicyVersion: data.Version, + LocalPart: localPart, + }, + &userapi.UpdatePolicyVersionResponse{}, + ); err != nil { + _, err = writer.Write([]byte("unable to update database")) + if err != nil { + logrus.WithError(err).Error("unable to write to database") + } + return &internalError + } + // display the privacy policy without a form + data.PublicVersion = false + data.HasConsented = true + + err = consentCfg.Templates.ExecuteTemplate(writer, consentCfg.Version+".gohtml", data) + if err != nil { + logrus.WithError(err).Error("unable to print consent template") + return &internalError } + return nil } + return &util.JSONResponse{Code: http.StatusOK} } func validHMAC(username, userHMAC, secret string) (bool, error) { diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index 467c6e78e4..ea05604a8f 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -119,19 +119,23 @@ func Setup( // unspecced consent tracking if cfg.Matrix.UserConsentOptions.Enabled() { - consentAPIMux.HandleFunc("/consent", consent(userAPI, cfg)).Methods(http.MethodGet, http.MethodPost, http.MethodOptions) + consentAPIMux.Handle("/consent", + httputil.MakeHTMLAPI("consent", func(writer http.ResponseWriter, request *http.Request) *util.JSONResponse { + return consent(writer, request, userAPI, cfg) + }), + ).Methods(http.MethodGet, http.MethodPost, http.MethodOptions) } r0mux := publicAPIMux.PathPrefix("/r0").Subrouter() unstableMux := publicAPIMux.PathPrefix("/unstable").Subrouter() r0mux.Handle("/createRoom", - httputil.MakeAuthAPI("createRoom", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("createRoom", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { return CreateRoom(req, device, cfg, accountDB, rsAPI, asAPI) }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/join/{roomIDOrAlias}", - httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -147,7 +151,7 @@ func Setup( if mscCfg.Enabled("msc2753") { r0mux.Handle("/peek/{roomIDOrAlias}", - httputil.MakeAuthAPI(gomatrixserverlib.Peek, userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI(gomatrixserverlib.Peek, userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -162,12 +166,12 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) } r0mux.Handle("/joined_rooms", - httputil.MakeAuthAPI("joined_rooms", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("joined_rooms", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetJoinedRooms(req, device, rsAPI) }), ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/join", - httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -181,7 +185,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/leave", - httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -195,7 +199,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/unpeek", - httputil.MakeAuthAPI("unpeek", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("unpeek", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -206,7 +210,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/ban", - httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -215,7 +219,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/invite", - httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -227,7 +231,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/kick", - httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -236,7 +240,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/unban", - httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -245,7 +249,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/send/{eventType}", - httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -254,7 +258,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/send/{eventType}/{txnID}", - httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -265,7 +269,7 @@ func Setup( }), ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/event/{eventID}", - httputil.MakeAuthAPI("rooms_get_event", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_get_event", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -274,7 +278,7 @@ func Setup( }), ).Methods(http.MethodGet, http.MethodOptions) - r0mux.Handle("/rooms/{roomID}/state", httputil.MakeAuthAPI("room_state", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + r0mux.Handle("/rooms/{roomID}/state", httputil.MakeAuthAPI("room_state", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -282,7 +286,7 @@ func Setup( return OnIncomingStateRequest(req.Context(), device, rsAPI, vars["roomID"]) })).Methods(http.MethodGet, http.MethodOptions) - r0mux.Handle("/rooms/{roomID}/aliases", httputil.MakeAuthAPI("aliases", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + r0mux.Handle("/rooms/{roomID}/aliases", httputil.MakeAuthAPI("aliases", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -290,7 +294,7 @@ func Setup( return GetAliases(req, rsAPI, device, vars["roomID"]) })).Methods(http.MethodGet, http.MethodOptions) - r0mux.Handle("/rooms/{roomID}/state/{type:[^/]+/?}", httputil.MakeAuthAPI("room_state", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + r0mux.Handle("/rooms/{roomID}/state/{type:[^/]+/?}", httputil.MakeAuthAPI("room_state", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -301,7 +305,7 @@ func Setup( return OnIncomingStateTypeRequest(req.Context(), device, rsAPI, vars["roomID"], eventType, "", eventFormat) })).Methods(http.MethodGet, http.MethodOptions) - r0mux.Handle("/rooms/{roomID}/state/{type}/{stateKey}", httputil.MakeAuthAPI("room_state", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + r0mux.Handle("/rooms/{roomID}/state/{type}/{stateKey}", httputil.MakeAuthAPI("room_state", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -311,7 +315,7 @@ func Setup( })).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/state/{eventType:[^/]+/?}", - httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -323,7 +327,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/state/{eventType}/{stateKey}", - httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -358,7 +362,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/directory/room/{roomAlias}", - httputil.MakeAuthAPI("directory_room", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("directory_room", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -368,7 +372,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/directory/room/{roomAlias}", - httputil.MakeAuthAPI("directory_room", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("directory_room", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -387,7 +391,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) // TODO: Add AS support r0mux.Handle("/directory/list/room/{roomID}", - httputil.MakeAuthAPI("directory_list", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("directory_list", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -402,19 +406,19 @@ func Setup( ).Methods(http.MethodGet, http.MethodPost, http.MethodOptions) r0mux.Handle("/logout", - httputil.MakeAuthAPI("logout", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("logout", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { return Logout(req, userAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/logout/all", - httputil.MakeAuthAPI("logout", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("logout", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { return LogoutAll(req, userAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/typing/{userID}", - httputil.MakeAuthAPI("rooms_typing", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_typing", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -426,7 +430,7 @@ func Setup( }), ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/redact/{eventID}", - httputil.MakeAuthAPI("rooms_redact", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_redact", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -435,7 +439,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/redact/{eventID}/{txnId}", - httputil.MakeAuthAPI("rooms_redact", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_redact", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -445,7 +449,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/sendToDevice/{eventType}/{txnID}", - httputil.MakeAuthAPI("send_to_device", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_to_device", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -459,7 +463,7 @@ func Setup( // rather than r0. It's an exact duplicate of the above handler. // TODO: Remove this if/when sytest is fixed! unstableMux.Handle("/sendToDevice/{eventType}/{txnID}", - httputil.MakeAuthAPI("send_to_device", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_to_device", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -470,7 +474,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/account/whoami", - httputil.MakeAuthAPI("whoami", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("whoami", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -479,7 +483,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/account/password", - httputil.MakeAuthAPI("password", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("password", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -488,7 +492,7 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/account/deactivate", - httputil.MakeAuthAPI("deactivate", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("deactivate", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -556,7 +560,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/profile/{userID}/avatar_url", - httputil.MakeAuthAPI("profile_avatar_url", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("profile_avatar_url", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -581,7 +585,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/profile/{userID}/displayname", - httputil.MakeAuthAPI("profile_displayname", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("profile_displayname", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -596,19 +600,19 @@ func Setup( // PUT requests, so we need to allow this method r0mux.Handle("/account/3pid", - httputil.MakeAuthAPI("account_3pid", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("account_3pid", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetAssociated3PIDs(req, accountDB, device) }), ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/account/3pid", - httputil.MakeAuthAPI("account_3pid", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("account_3pid", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { return CheckAndSave3PIDAssociation(req, accountDB, device, cfg) }), ).Methods(http.MethodPost, http.MethodOptions) unstableMux.Handle("/account/3pid/delete", - httputil.MakeAuthAPI("account_3pid", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("account_3pid", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { return Forget3PID(req, accountDB) }), ).Methods(http.MethodPost, http.MethodOptions) @@ -634,7 +638,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/voip/turnServer", - httputil.MakeAuthAPI("turn_server", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("turn_server", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -663,7 +667,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/user/{userID}/account_data/{type}", - httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -673,7 +677,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/user/{userID}/rooms/{roomID}/account_data/{type}", - httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -683,7 +687,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/user/{userID}/account_data/{type}", - httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -693,7 +697,7 @@ func Setup( ).Methods(http.MethodGet) r0mux.Handle("/user/{userID}/rooms/{roomID}/account_data/{type}", - httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -703,7 +707,7 @@ func Setup( ).Methods(http.MethodGet) r0mux.Handle("/admin/whois/{userID}", - httputil.MakeAuthAPI("admin_whois", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("admin_whois", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -713,7 +717,7 @@ func Setup( ).Methods(http.MethodGet) r0mux.Handle("/user/{userID}/openid/request_token", - httputil.MakeAuthAPI("openid_request_token", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("openid_request_token", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -726,7 +730,7 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/user_directory/search", - httputil.MakeAuthAPI("userdirectory_search", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("userdirectory_search", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -751,7 +755,7 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/members", - httputil.MakeAuthAPI("rooms_members", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_members", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -761,7 +765,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/joined_members", - httputil.MakeAuthAPI("rooms_members", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_members", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -771,7 +775,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/read_markers", - httputil.MakeAuthAPI("rooms_read_markers", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_read_markers", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -784,7 +788,7 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/forget", - httputil.MakeAuthAPI("rooms_forget", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_forget", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -797,13 +801,13 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/devices", - httputil.MakeAuthAPI("get_devices", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_devices", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetDevicesByLocalpart(req, userAPI, device) }), ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/devices/{deviceID}", - httputil.MakeAuthAPI("get_device", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_device", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -813,7 +817,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/devices/{deviceID}", - httputil.MakeAuthAPI("device_data", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("device_data", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -823,7 +827,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/devices/{deviceID}", - httputil.MakeAuthAPI("delete_device", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("delete_device", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -833,7 +837,7 @@ func Setup( ).Methods(http.MethodDelete, http.MethodOptions) r0mux.Handle("/delete_devices", - httputil.MakeAuthAPI("delete_devices", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("delete_devices", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { return DeleteDevices(req, userAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) @@ -858,7 +862,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/user/{userId}/rooms/{roomId}/tags", - httputil.MakeAuthAPI("get_tags", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_tags", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -868,7 +872,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/user/{userId}/rooms/{roomId}/tags/{tag}", - httputil.MakeAuthAPI("put_tag", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("put_tag", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -878,7 +882,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/user/{userId}/rooms/{roomId}/tags/{tag}", - httputil.MakeAuthAPI("delete_tag", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("delete_tag", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -888,7 +892,7 @@ func Setup( ).Methods(http.MethodDelete, http.MethodOptions) r0mux.Handle("/capabilities", - httputil.MakeAuthAPI("capabilities", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("capabilities", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -898,7 +902,7 @@ func Setup( // Key Backup Versions (Metadata) - getBackupKeysVersion := httputil.MakeAuthAPI("get_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getBackupKeysVersion := httputil.MakeAuthAPI("get_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -906,11 +910,11 @@ func Setup( return KeyBackupVersion(req, userAPI, device, vars["version"]) }) - getLatestBackupKeysVersion := httputil.MakeAuthAPI("get_latest_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getLatestBackupKeysVersion := httputil.MakeAuthAPI("get_latest_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { return KeyBackupVersion(req, userAPI, device, "") }) - putBackupKeysVersion := httputil.MakeAuthAPI("put_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + putBackupKeysVersion := httputil.MakeAuthAPI("put_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -918,7 +922,7 @@ func Setup( return ModifyKeyBackupVersionAuthData(req, userAPI, device, vars["version"]) }) - deleteBackupKeysVersion := httputil.MakeAuthAPI("delete_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + deleteBackupKeysVersion := httputil.MakeAuthAPI("delete_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -926,7 +930,7 @@ func Setup( return DeleteKeyBackupVersion(req, userAPI, device, vars["version"]) }) - postNewBackupKeysVersion := httputil.MakeAuthAPI("post_new_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + postNewBackupKeysVersion := httputil.MakeAuthAPI("post_new_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { return CreateKeyBackupVersion(req, userAPI, device) }) @@ -945,7 +949,7 @@ func Setup( // Inserting E2E Backup Keys // Bulk room and session - putBackupKeys := httputil.MakeAuthAPI("put_backup_keys", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + putBackupKeys := httputil.MakeAuthAPI("put_backup_keys", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { version := req.URL.Query().Get("version") if version == "" { return util.JSONResponse{ @@ -962,7 +966,7 @@ func Setup( }) // Single room bulk session - putBackupKeysRoom := httputil.MakeAuthAPI("put_backup_keys_room", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + putBackupKeysRoom := httputil.MakeAuthAPI("put_backup_keys_room", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -994,7 +998,7 @@ func Setup( }) // Single room, single session - putBackupKeysRoomSession := httputil.MakeAuthAPI("put_backup_keys_room_session", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + putBackupKeysRoomSession := httputil.MakeAuthAPI("put_backup_keys_room_session", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -1036,11 +1040,11 @@ func Setup( // Querying E2E Backup Keys - getBackupKeys := httputil.MakeAuthAPI("get_backup_keys", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getBackupKeys := httputil.MakeAuthAPI("get_backup_keys", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetBackupKeys(req, userAPI, device, req.URL.Query().Get("version"), "", "") }) - getBackupKeysRoom := httputil.MakeAuthAPI("get_backup_keys_room", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getBackupKeysRoom := httputil.MakeAuthAPI("get_backup_keys_room", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -1048,7 +1052,7 @@ func Setup( return GetBackupKeys(req, userAPI, device, req.URL.Query().Get("version"), vars["roomID"], "") }) - getBackupKeysRoomSession := httputil.MakeAuthAPI("get_backup_keys_room_session", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getBackupKeysRoomSession := httputil.MakeAuthAPI("get_backup_keys_room_session", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -1068,11 +1072,11 @@ func Setup( // Cross-signing device keys - postDeviceSigningKeys := httputil.MakeAuthAPI("post_device_signing_keys", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + postDeviceSigningKeys := httputil.MakeAuthAPI("post_device_signing_keys", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { return UploadCrossSigningDeviceKeys(req, userInteractiveAuth, keyAPI, device, accountDB, cfg) }) - postDeviceSigningSignatures := httputil.MakeAuthAPI("post_device_signing_signatures", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + postDeviceSigningSignatures := httputil.MakeAuthAPI("post_device_signing_signatures", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { return UploadCrossSigningDeviceSignatures(req, keyAPI, device) }) @@ -1084,27 +1088,27 @@ func Setup( // Supplying a device ID is deprecated. r0mux.Handle("/keys/upload/{deviceID}", - httputil.MakeAuthAPI("keys_upload", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("keys_upload", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { return UploadKeys(req, keyAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/keys/upload", - httputil.MakeAuthAPI("keys_upload", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("keys_upload", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { return UploadKeys(req, keyAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/keys/query", - httputil.MakeAuthAPI("keys_query", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("keys_query", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { return QueryKeys(req, keyAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/keys/claim", - httputil.MakeAuthAPI("keys_claim", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("keys_claim", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { return ClaimKeys(req, keyAPI) }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomId}/receipt/{receiptType}/{eventId}", - httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } diff --git a/internal/httputil/httpapi.go b/internal/httputil/httpapi.go index 63ba9dada4..89b43b3a6a 100644 --- a/internal/httputil/httpapi.go +++ b/internal/httputil/httpapi.go @@ -15,7 +15,11 @@ package httputil import ( + "bytes" "context" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" "fmt" "io" "net/http" @@ -54,6 +58,7 @@ func MakeAuthAPI( metricsName string, userAPI userapi.UserInternalAPI, userConsentCfg config.UserConsentOptions, + requireConsent bool, f func(*http.Request, *userapi.Device) util.JSONResponse, ) http.Handler { h := func(req *http.Request) util.JSONResponse { @@ -82,26 +87,12 @@ func MakeAuthAPI( } }() - // check if the user accepted any policy - if userConsentCfg.Enabled() { - localPart, _, err := gomatrixserverlib.SplitID('@', device.UserID) - if err != nil { - return jsonerror.InternalServerError() - } - // check which version of the policy the user accepted - res := &userapi.QueryPolicyVersionResponse{} - err = userAPI.QueryPolicyVersion(req.Context(), &userapi.QueryPolicyVersionRequest{ - LocalPart: localPart, - }, res) - if err != nil { - return jsonerror.InternalServerError() - } - - // user hasn't accepted any policy, block access. - if userConsentCfg.Version != res.PolicyVersion { + if userConsentCfg.Enabled() && requireConsent { + consentError := checkConsent(req.Context(), device.UserID, userAPI, userConsentCfg) + if consentError != nil { return util.JSONResponse{ Code: http.StatusForbidden, - JSON: jsonerror.Forbidden(userConsentCfg.BlockEventsError), + JSON: consentError, } } } @@ -117,6 +108,56 @@ func MakeAuthAPI( return MakeExternalAPI(metricsName, h) } +func checkConsent(ctx context.Context, userID string, userAPI userapi.UserInternalAPI, userConsentCfg config.UserConsentOptions) error { + localPart, _, err := gomatrixserverlib.SplitID('@', userID) + if err != nil { + return nil + } + // check which version of the policy the user accepted + res := &userapi.QueryPolicyVersionResponse{} + err = userAPI.QueryPolicyVersion(ctx, &userapi.QueryPolicyVersionRequest{ + LocalPart: localPart, + }, res) + if err != nil { + return nil + } + + // user hasn't accepted any policy, block access. + if userConsentCfg.Version != res.PolicyVersion { + uri, err := getConsentURL(userID, userConsentCfg) + if err != nil { + return jsonerror.Unknown("unable to get consent URL") + } + msg := &bytes.Buffer{} + c := struct { + ConsentURL string + }{ + ConsentURL: uri, + } + if err = userConsentCfg.BlockEventsTemplate.ExecuteTemplate(msg, "blockEventsError", c); err != nil { + logrus.Infof("error consent message: %+v", err) + return jsonerror.Unknown("unable to get consent URL") + } + return jsonerror.ConsentNotGiven(uri, msg.String()) + } + return nil +} + +// getConsentURL constructs the URL shown to users to accept the TOS +func getConsentURL(username string, config config.UserConsentOptions) (string, error) { + mac := hmac.New(sha256.New, []byte(config.FormSecret)) + _, err := mac.Write([]byte(username)) + if err != nil { + return "", err + } + hmac := hex.EncodeToString(mac.Sum(nil)) + + return fmt.Sprintf( + "%s/_matrix/consent?u=%s&h=%s&v=%s", + config.BaseURL, username, hmac, config.Version, + ), nil +} + // MakeExternalAPI turns a util.JSONRequestHandler function into an http.Handler. // This is used for APIs that are called from the internet. func MakeExternalAPI(metricsName string, f func(*http.Request) util.JSONResponse) http.Handler { diff --git a/setup/config/config_global.go b/setup/config/config_global.go index 4ef8430444..d5ad9333e8 100644 --- a/setup/config/config_global.go +++ b/setup/config/config_global.go @@ -5,6 +5,7 @@ import ( "html/template" "math/rand" "path/filepath" + textTemplate "text/template" "time" "github.com/matrix-org/gomatrixserverlib" @@ -15,6 +16,9 @@ type Global struct { // The name of the server. This is usually the domain name, e.g 'matrix.org', 'localhost'. ServerName gomatrixserverlib.ServerName `yaml:"server_name"` + // The base URL this homeserver will server clients on, e.g. https://matrix.org + BaseURL string `yaml:"base_url"` + // Path to the private key which will be used to sign requests and events. PrivateKeyPath Path `yaml:"private_key"` @@ -68,6 +72,7 @@ type Global struct { func (c *Global) Defaults(generate bool) { if generate { c.ServerName = "localhost" + c.BaseURL = "http://localhost" c.PrivateKeyPath = "matrix_key.pem" _, c.PrivateKey, _ = ed25519.GenerateKey(rand.New(rand.NewSource(0))) c.KeyID = "ed25519:auto" @@ -78,7 +83,7 @@ func (c *Global) Defaults(generate bool) { c.Metrics.Defaults(generate) c.DNSCache.Defaults() c.Sentry.Defaults() - c.UserConsentOptions.Defaults() + c.UserConsentOptions.Defaults(c.BaseURL) } func (c *Global) Verify(configErrs *ConfigErrors, isMonolith bool) { @@ -229,15 +234,19 @@ type UserConsentOptions struct { // The error message to display if the user hasn't given their consent yet BlockEventsError string `yaml:"block_events_error"` // All loaded templates - Templates *template.Template `yaml:"-"` + Templates *template.Template `yaml:"-"` + BlockEventsTemplate *textTemplate.Template `yaml:"-"` + // + BaseURL string `yaml:"-"` } -func (c *UserConsentOptions) Defaults() { +func (c *UserConsentOptions) Defaults(baseURL string) { c.RequireAtRegistration = false c.SendServerNoticeToGuest = false c.PolicyName = "Privacy Policy" c.Version = "1.0" c.TemplateDir = "./templates/privacy" + c.BaseURL = baseURL } func (c *UserConsentOptions) Verify(configErrors *ConfigErrors, isMonolith bool) { @@ -256,6 +265,8 @@ func (c *UserConsentOptions) Verify(configErrors *ConfigErrors, isMonolith bool) return } + c.BlockEventsTemplate = textTemplate.Must(textTemplate.New("blockEventsError").Parse(c.BlockEventsError)) + // Read all defined *.gohtml templates t, err := template.ParseGlob(filepath.Join(p, "*.gohtml")) if err != nil || t == nil { diff --git a/setup/flags.go b/setup/flags.go index 281cf33925..2f06346197 100644 --- a/setup/flags.go +++ b/setup/flags.go @@ -43,7 +43,8 @@ func ParseFlags(monolith bool) *config.Dendrite { } cfg, err := config.Load(*configPath, monolith) - + // TODO: just for testing + cfg.Global.UserConsentOptions.BaseURL = cfg.Global.BaseURL if err != nil { logrus.Fatalf("Invalid config file: %s", err) } diff --git a/setup/mscs/msc2836/msc2836.go b/setup/mscs/msc2836/msc2836.go index 8679e19959..6ad79be961 100644 --- a/setup/mscs/msc2836/msc2836.go +++ b/setup/mscs/msc2836/msc2836.go @@ -126,7 +126,7 @@ func Enable( }) base.PublicClientAPIMux.Handle("/unstable/event_relationships", - httputil.MakeAuthAPI("eventRelationships", userAPI, base.Cfg.Global.UserConsentOptions, eventRelationshipHandler(db, rsAPI, fsAPI)), + httputil.MakeAuthAPI("eventRelationships", userAPI, base.Cfg.Global.UserConsentOptions, false, eventRelationshipHandler(db, rsAPI, fsAPI)), ).Methods(http.MethodPost, http.MethodOptions) base.PublicFederationAPIMux.Handle("/unstable/event_relationships", httputil.MakeExternalAPI( diff --git a/setup/mscs/msc2946/msc2946.go b/setup/mscs/msc2946/msc2946.go index 5f20042d27..7be4535547 100644 --- a/setup/mscs/msc2946/msc2946.go +++ b/setup/mscs/msc2946/msc2946.go @@ -71,7 +71,7 @@ func Enable( }) base.PublicClientAPIMux.Handle("/unstable/org.matrix.msc2946/rooms/{roomID}/spaces", - httputil.MakeAuthAPI("spaces", userAPI, base.Cfg.Global.UserConsentOptions, spacesHandler(db, rsAPI, fsAPI, base.Cfg.Global.ServerName)), + httputil.MakeAuthAPI("spaces", userAPI, base.Cfg.Global.UserConsentOptions, false, spacesHandler(db, rsAPI, fsAPI, base.Cfg.Global.ServerName)), ).Methods(http.MethodPost, http.MethodOptions) base.PublicFederationAPIMux.Handle("/unstable/org.matrix.msc2946/spaces/{roomID}", httputil.MakeExternalAPI( diff --git a/syncapi/routing/routing.go b/syncapi/routing/routing.go index a1c38300c4..b3c1da88b4 100644 --- a/syncapi/routing/routing.go +++ b/syncapi/routing/routing.go @@ -42,11 +42,11 @@ func Setup( r0mux := csMux.PathPrefix("/r0").Subrouter() // TODO: Add AS support for all handlers below. - r0mux.Handle("/sync", httputil.MakeAuthAPI("sync", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + r0mux.Handle("/sync", httputil.MakeAuthAPI("sync", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { return srp.OnIncomingSyncRequest(req, device) })).Methods(http.MethodGet, http.MethodOptions) - r0mux.Handle("/rooms/{roomID}/messages", httputil.MakeAuthAPI("room_messages", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + r0mux.Handle("/rooms/{roomID}/messages", httputil.MakeAuthAPI("room_messages", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -55,7 +55,7 @@ func Setup( })).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/user/{userId}/filter", - httputil.MakeAuthAPI("put_filter", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("put_filter", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -65,7 +65,7 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/user/{userId}/filter/{filterId}", - httputil.MakeAuthAPI("get_filter", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_filter", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -74,7 +74,7 @@ func Setup( }), ).Methods(http.MethodGet, http.MethodOptions) - r0mux.Handle("/keys/changes", httputil.MakeAuthAPI("keys_changes", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + r0mux.Handle("/keys/changes", httputil.MakeAuthAPI("keys_changes", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { return srp.OnIncomingKeyChangeRequest(req, device) })).Methods(http.MethodGet, http.MethodOptions) } From f8bebe5e5ad1e817dd5cbedd463a777359c8e707 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Tue, 15 Feb 2022 14:10:50 +0100 Subject: [PATCH 14/56] Add policy_version to insertAccount statement --- userapi/api/api.go | 12 ++++++------ userapi/internal/api.go | 2 +- userapi/storage/accounts/interface.go | 2 +- userapi/storage/accounts/postgres/accounts_table.go | 8 ++++---- userapi/storage/accounts/postgres/storage.go | 10 +++++----- userapi/storage/accounts/sqlite3/accounts_table.go | 8 ++++---- userapi/storage/accounts/sqlite3/storage.go | 10 +++++----- userapi/userapi_test.go | 4 ++-- 8 files changed, 28 insertions(+), 28 deletions(-) diff --git a/userapi/api/api.go b/userapi/api/api.go index f38180a63f..c468fc0f6e 100644 --- a/userapi/api/api.go +++ b/userapi/api/api.go @@ -245,12 +245,12 @@ type QuerySearchProfilesResponse struct { // PerformAccountCreationRequest is the request for PerformAccountCreation type PerformAccountCreationRequest struct { - AccountType AccountType // Required: whether this is a guest or user account - Localpart string // Required: The localpart for this account. Ignored if account type is guest. - - AppServiceID string // optional: the application service ID (not user ID) creating this account, if any. - Password string // optional: if missing then this account will be a passwordless account - OnConflict Conflict + AccountType AccountType // Required: whether this is a guest or user account + Localpart string // Required: The localpart for this account. Ignored if account type is guest. + PolicyVersion string // optional: the privacy policy this account has accepted + AppServiceID string // optional: the application service ID (not user ID) creating this account, if any. + Password string // optional: if missing then this account will be a passwordless account + OnConflict Conflict } // PerformAccountCreationResponse is the response for PerformAccountCreation diff --git a/userapi/internal/api.go b/userapi/internal/api.go index a6b933e3f1..1c306cefad 100644 --- a/userapi/internal/api.go +++ b/userapi/internal/api.go @@ -67,7 +67,7 @@ func (a *UserInternalAPI) PerformAccountCreation(ctx context.Context, req *api.P res.Account = acc return nil } - acc, err := a.AccountDB.CreateAccount(ctx, req.Localpart, req.Password, req.AppServiceID) + acc, err := a.AccountDB.CreateAccount(ctx, req.Localpart, req.Password, req.AppServiceID, req.PolicyVersion) if err != nil { if errors.Is(err, sqlutil.ErrUserExists) { // This account already exists switch req.OnConflict { diff --git a/userapi/storage/accounts/interface.go b/userapi/storage/accounts/interface.go index 61f59c9b98..81224af1a6 100644 --- a/userapi/storage/accounts/interface.go +++ b/userapi/storage/accounts/interface.go @@ -32,7 +32,7 @@ type Database interface { // CreateAccount makes a new account with the given login name and password, and creates an empty profile // for this account. If no password is supplied, the account will be a passwordless account. If the // account already exists, it will return nil, ErrUserExists. - CreateAccount(ctx context.Context, localpart, plaintextPassword, appserviceID string) (*api.Account, error) + CreateAccount(ctx context.Context, localpart, plaintextPassword, appserviceID, policyVersion string) (*api.Account, error) CreateGuestAccount(ctx context.Context) (*api.Account, error) SaveAccountData(ctx context.Context, localpart, roomID, dataType string, content json.RawMessage) error GetAccountData(ctx context.Context, localpart string) (global map[string]json.RawMessage, rooms map[string]map[string]json.RawMessage, err error) diff --git a/userapi/storage/accounts/postgres/accounts_table.go b/userapi/storage/accounts/postgres/accounts_table.go index 1b22a44e4c..d79ed05352 100644 --- a/userapi/storage/accounts/postgres/accounts_table.go +++ b/userapi/storage/accounts/postgres/accounts_table.go @@ -50,7 +50,7 @@ CREATE SEQUENCE IF NOT EXISTS numeric_username_seq START 1; ` const insertAccountSQL = "" + - "INSERT INTO account_accounts(localpart, created_ts, password_hash, appservice_id) VALUES ($1, $2, $3, $4)" + "INSERT INTO account_accounts(localpart, created_ts, password_hash, appservice_id, policy_version) VALUES ($1, $2, $3, $4, $5)" const updatePasswordSQL = "" + "UPDATE account_accounts SET password_hash = $1 WHERE localpart = $2" @@ -113,16 +113,16 @@ func (s *accountsStatements) prepare(db *sql.DB, server gomatrixserverlib.Server // this account will be passwordless. Returns an error if this account already exists. Returns the account // on success. func (s *accountsStatements) insertAccount( - ctx context.Context, txn *sql.Tx, localpart, hash, appserviceID string, + ctx context.Context, txn *sql.Tx, localpart, hash, appserviceID, policyVersion string, ) (*api.Account, error) { createdTimeMS := time.Now().UnixNano() / 1000000 stmt := sqlutil.TxStmt(txn, s.insertAccountStmt) var err error if appserviceID == "" { - _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, nil) + _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, nil, "") } else { - _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID) + _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID, policyVersion) } if err != nil { return nil, err diff --git a/userapi/storage/accounts/postgres/storage.go b/userapi/storage/accounts/postgres/storage.go index 4e5aa20a79..14ddd0e2fa 100644 --- a/userapi/storage/accounts/postgres/storage.go +++ b/userapi/storage/accounts/postgres/storage.go @@ -166,7 +166,7 @@ func (d *Database) CreateGuestAccount(ctx context.Context) (acc *api.Account, er return err } localpart := strconv.FormatInt(numLocalpart, 10) - acc, err = d.createAccount(ctx, txn, localpart, "", "") + acc, err = d.createAccount(ctx, txn, localpart, "", "", "") return err }) return acc, err @@ -176,17 +176,17 @@ func (d *Database) CreateGuestAccount(ctx context.Context) (acc *api.Account, er // for this account. If no password is supplied, the account will be a passwordless account. If the // account already exists, it will return nil, sqlutil.ErrUserExists. func (d *Database) CreateAccount( - ctx context.Context, localpart, plaintextPassword, appserviceID string, + ctx context.Context, localpart, plaintextPassword, appserviceID, policyVersion string, ) (acc *api.Account, err error) { err = sqlutil.WithTransaction(d.db, func(txn *sql.Tx) error { - acc, err = d.createAccount(ctx, txn, localpart, plaintextPassword, appserviceID) + acc, err = d.createAccount(ctx, txn, localpart, plaintextPassword, appserviceID, policyVersion) return err }) return } func (d *Database) createAccount( - ctx context.Context, txn *sql.Tx, localpart, plaintextPassword, appserviceID string, + ctx context.Context, txn *sql.Tx, localpart, plaintextPassword, appserviceID, policyVersion string, ) (*api.Account, error) { var account *api.Account var err error @@ -198,7 +198,7 @@ func (d *Database) createAccount( return nil, err } } - if account, err = d.accounts.insertAccount(ctx, txn, localpart, hash, appserviceID); err != nil { + if account, err = d.accounts.insertAccount(ctx, txn, localpart, hash, appserviceID, policyVersion); err != nil { if sqlutil.IsUniqueConstraintViolationErr(err) { return nil, sqlutil.ErrUserExists } diff --git a/userapi/storage/accounts/sqlite3/accounts_table.go b/userapi/storage/accounts/sqlite3/accounts_table.go index f64c05717a..f1c24e2208 100644 --- a/userapi/storage/accounts/sqlite3/accounts_table.go +++ b/userapi/storage/accounts/sqlite3/accounts_table.go @@ -48,7 +48,7 @@ CREATE TABLE IF NOT EXISTS account_accounts ( ` const insertAccountSQL = "" + - "INSERT INTO account_accounts(localpart, created_ts, password_hash, appservice_id) VALUES ($1, $2, $3, $4)" + "INSERT INTO account_accounts(localpart, created_ts, password_hash, appservice_id, policy_version) VALUES ($1, $2, $3, $4, $5)" const updatePasswordSQL = "" + "UPDATE account_accounts SET password_hash = $1 WHERE localpart = $2" @@ -113,16 +113,16 @@ func (s *accountsStatements) prepare(db *sql.DB, server gomatrixserverlib.Server // this account will be passwordless. Returns an error if this account already exists. Returns the account // on success. func (s *accountsStatements) insertAccount( - ctx context.Context, txn *sql.Tx, localpart, hash, appserviceID string, + ctx context.Context, txn *sql.Tx, localpart, hash, appserviceID, policyVersion string, ) (*api.Account, error) { createdTimeMS := time.Now().UnixNano() / 1000000 stmt := s.insertAccountStmt var err error if appserviceID == "" { - _, err = sqlutil.TxStmt(txn, stmt).ExecContext(ctx, localpart, createdTimeMS, hash, nil) + _, err = sqlutil.TxStmt(txn, stmt).ExecContext(ctx, localpart, createdTimeMS, hash, nil, "") } else { - _, err = sqlutil.TxStmt(txn, stmt).ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID) + _, err = sqlutil.TxStmt(txn, stmt).ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID, policyVersion) } if err != nil { return nil, err diff --git a/userapi/storage/accounts/sqlite3/storage.go b/userapi/storage/accounts/sqlite3/storage.go index 768d58f658..0fcd653472 100644 --- a/userapi/storage/accounts/sqlite3/storage.go +++ b/userapi/storage/accounts/sqlite3/storage.go @@ -192,7 +192,7 @@ func (d *Database) CreateGuestAccount(ctx context.Context) (acc *api.Account, er return err } localpart := strconv.FormatInt(numLocalpart, 10) - acc, err = d.createAccount(ctx, txn, localpart, "", "") + acc, err = d.createAccount(ctx, txn, localpart, "", "", "") return err }) return acc, err @@ -202,7 +202,7 @@ func (d *Database) CreateGuestAccount(ctx context.Context) (acc *api.Account, er // for this account. If no password is supplied, the account will be a passwordless account. If the // account already exists, it will return nil, ErrUserExists. func (d *Database) CreateAccount( - ctx context.Context, localpart, plaintextPassword, appserviceID string, + ctx context.Context, localpart, plaintextPassword, appserviceID, policyVersion string, ) (acc *api.Account, err error) { // Create one account at a time else we can get 'database is locked'. d.profilesMu.Lock() @@ -212,7 +212,7 @@ func (d *Database) CreateAccount( defer d.accountDatasMu.Unlock() defer d.accountsMu.Unlock() err = d.writer.Do(d.db, nil, func(txn *sql.Tx) error { - acc, err = d.createAccount(ctx, txn, localpart, plaintextPassword, appserviceID) + acc, err = d.createAccount(ctx, txn, localpart, plaintextPassword, appserviceID, policyVersion) return err }) return @@ -221,7 +221,7 @@ func (d *Database) CreateAccount( // WARNING! This function assumes that the relevant mutexes have already // been taken out by the caller (e.g. CreateAccount or CreateGuestAccount). func (d *Database) createAccount( - ctx context.Context, txn *sql.Tx, localpart, plaintextPassword, appserviceID string, + ctx context.Context, txn *sql.Tx, localpart, plaintextPassword, appserviceID, policyVersion string, ) (*api.Account, error) { var err error var account *api.Account @@ -233,7 +233,7 @@ func (d *Database) createAccount( return nil, err } } - if account, err = d.accounts.insertAccount(ctx, txn, localpart, hash, appserviceID); err != nil { + if account, err = d.accounts.insertAccount(ctx, txn, localpart, hash, appserviceID, policyVersion); err != nil { return nil, sqlutil.ErrUserExists } if err = d.profiles.insertProfile(ctx, txn, localpart); err != nil { diff --git a/userapi/userapi_test.go b/userapi/userapi_test.go index 266f5ed587..5dd217828a 100644 --- a/userapi/userapi_test.go +++ b/userapi/userapi_test.go @@ -73,7 +73,7 @@ func TestQueryProfile(t *testing.T) { aliceAvatarURL := "mxc://example.com/alice" aliceDisplayName := "Alice" userAPI, accountDB := MustMakeInternalAPI(t, apiTestOpts{}) - _, err := accountDB.CreateAccount(context.TODO(), "alice", "foobar", "") + _, err := accountDB.CreateAccount(context.TODO(), "alice", "foobar", "", "") if err != nil { t.Fatalf("failed to make account: %s", err) } @@ -151,7 +151,7 @@ func TestLoginToken(t *testing.T) { t.Run("tokenLoginFlow", func(t *testing.T) { userAPI, accountDB := MustMakeInternalAPI(t, apiTestOpts{}) - _, err := accountDB.CreateAccount(ctx, "auser", "apassword", "") + _, err := accountDB.CreateAccount(ctx, "auser", "apassword", "", "") if err != nil { t.Fatalf("failed to make account: %s", err) } From cbdbbb08393222709a8cb9ba7ee4e094ece9cdd5 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Tue, 15 Feb 2022 14:13:22 +0100 Subject: [PATCH 15/56] Make sure we use the correct login stages --- setup/config/config.go | 51 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/setup/config/config.go b/setup/config/config.go index eb371a54b5..90eef3320a 100644 --- a/setup/config/config.go +++ b/setup/config/config.go @@ -263,6 +263,21 @@ func loadConfig( return &c, nil } +type Terms struct { + Policies Policies `json:"policies"` +} +type En struct { + Name string `json:"name"` + URL string `json:"url"` +} +type PrivacyPolicy struct { + En En `json:"en"` + Version string `json:"version"` +} +type Policies struct { + PrivacyPolicy PrivacyPolicy `json:"privacy_policy"` +} + // Derive generates data that is derived from various values provided in // the config file. func (config *Dendrite) Derive() error { @@ -273,13 +288,39 @@ func (config *Dendrite) Derive() error { // TODO: Add email auth type // TODO: Add MSISDN auth type + if config.Global.UserConsentOptions.RequireAtRegistration { + uri := config.Global.BaseURL + "/_matrix/consent?v=" + config.Global.UserConsentOptions.Version + config.Derived.Registration.Params[authtypes.LoginTypeTerms] = Terms{ + Policies: Policies{ + PrivacyPolicy: PrivacyPolicy{ + En: En{ + Name: config.Global.UserConsentOptions.PolicyName, + URL: uri, + }, + Version: config.Global.UserConsentOptions.Version, + }, + }, + } + } if config.ClientAPI.RecaptchaEnabled { config.Derived.Registration.Params[authtypes.LoginTypeRecaptcha] = map[string]string{"public_key": config.ClientAPI.RecaptchaPublicKey} - config.Derived.Registration.Flows = append(config.Derived.Registration.Flows, - authtypes.Flow{Stages: []authtypes.LoginType{authtypes.LoginTypeRecaptcha}}) - } else { - config.Derived.Registration.Flows = append(config.Derived.Registration.Flows, - authtypes.Flow{Stages: []authtypes.LoginType{authtypes.LoginTypeDummy}}) + } + + if config.Derived.Registration.Flows == nil { + config.Derived.Registration.Flows = append(config.Derived.Registration.Flows, authtypes.Flow{ + Stages: []authtypes.LoginType{authtypes.LoginTypeDummy}, + }) + } + + // prepend each flow with LoginTypeTerms or LoginTypeRecaptcha + for i, flow := range config.Derived.Registration.Flows { + if config.Global.UserConsentOptions.RequireAtRegistration { + flow.Stages = append([]authtypes.LoginType{authtypes.LoginTypeTerms}, flow.Stages...) + } + if config.ClientAPI.RecaptchaEnabled { + flow.Stages = append([]authtypes.LoginType{authtypes.LoginTypeRecaptcha}, flow.Stages...) + } + config.Derived.Registration.Flows[i] = flow } // Load application service configuration files From 535d388ec08eaa08127b4e338beb91ba035613b1 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Tue, 15 Feb 2022 14:14:39 +0100 Subject: [PATCH 16/56] Add new login type "m.login.terms" --- clientapi/auth/authtypes/logintypes.go | 1 + clientapi/routing/register.go | 33 ++++++++++++++++++-------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/clientapi/auth/authtypes/logintypes.go b/clientapi/auth/authtypes/logintypes.go index f01e48f806..1952c270ab 100644 --- a/clientapi/auth/authtypes/logintypes.go +++ b/clientapi/auth/authtypes/logintypes.go @@ -11,4 +11,5 @@ const ( LoginTypeRecaptcha = "m.login.recaptcha" LoginTypeApplicationService = "m.login.application_service" LoginTypeToken = "m.login.token" + LoginTypeTerms = "m.login.terms" ) diff --git a/clientapi/routing/register.go b/clientapi/routing/register.go index 8823a41e36..53f8110882 100644 --- a/clientapi/routing/register.go +++ b/clientapi/routing/register.go @@ -153,7 +153,7 @@ type authDict struct { // http://matrix.org/speculator/spec/HEAD/client_server/unstable.html#user-interactive-authentication-api type userInteractiveResponse struct { Flows []authtypes.Flow `json:"flows"` - Completed []authtypes.LoginType `json:"completed"` + Completed []authtypes.LoginType `json:"completed,omitempty"` Params map[string]interface{} `json:"params"` Session string `json:"session"` } @@ -629,6 +629,8 @@ func handleRegistrationFlow( } switch r.Auth.Type { + case authtypes.LoginTypeTerms: + AddCompletedSessionStage(sessionID, authtypes.LoginTypeTerms) case authtypes.LoginTypeRecaptcha: // Check given captcha response resErr := validateRecaptcha(cfg, r.Auth.Response, req.RemoteAddr) @@ -696,11 +698,16 @@ func handleApplicationServiceRegistration( return *err } + policyVersion := "" + if cfg.Matrix.UserConsentOptions.Enabled() { + policyVersion = cfg.Matrix.UserConsentOptions.Version + } + // If no error, application service was successfully validated. // Don't need to worry about appending to registration stages as // application service registration is entirely separate. return completeRegistration( - req.Context(), userAPI, r.Username, "", appserviceID, req.RemoteAddr, req.UserAgent(), + req.Context(), userAPI, r.Username, "", appserviceID, req.RemoteAddr, req.UserAgent(), policyVersion, r.InhibitLogin, r.InitialDisplayName, r.DeviceID, ) } @@ -717,9 +724,14 @@ func checkAndCompleteFlow( userAPI userapi.UserInternalAPI, ) util.JSONResponse { if checkFlowCompleted(flow, cfg.Derived.Registration.Flows) { + policyVersion := "" + if cfg.Matrix.UserConsentOptions.Enabled() { + policyVersion = cfg.Matrix.UserConsentOptions.Version + } // This flow was completed, registration can continue + return completeRegistration( - req.Context(), userAPI, r.Username, r.Password, "", req.RemoteAddr, req.UserAgent(), + req.Context(), userAPI, r.Username, r.Password, "", req.RemoteAddr, req.UserAgent(), policyVersion, r.InhibitLogin, r.InitialDisplayName, r.DeviceID, ) } @@ -742,7 +754,7 @@ func checkAndCompleteFlow( func completeRegistration( ctx context.Context, userAPI userapi.UserInternalAPI, - username, password, appserviceID, ipAddr, userAgent string, + username, password, appserviceID, ipAddr, userAgent, policyVersion string, inhibitLogin eventutil.WeakBoolean, displayName, deviceID *string, ) util.JSONResponse { @@ -762,11 +774,12 @@ func completeRegistration( var accRes userapi.PerformAccountCreationResponse err := userAPI.PerformAccountCreation(ctx, &userapi.PerformAccountCreationRequest{ - AppServiceID: appserviceID, - Localpart: username, - Password: password, - AccountType: userapi.AccountTypeUser, - OnConflict: userapi.ConflictAbort, + AppServiceID: appserviceID, + Localpart: username, + Password: password, + AccountType: userapi.AccountTypeUser, + OnConflict: userapi.ConflictAbort, + PolicyVersion: policyVersion, }, &accRes) if err != nil { if _, ok := err.(*userapi.ErrorConflict); ok { // user already exists @@ -963,5 +976,5 @@ func handleSharedSecretRegistration(userAPI userapi.UserInternalAPI, sr *SharedS return *resErr } deviceID := "shared_secret_registration" - return completeRegistration(req.Context(), userAPI, ssrr.User, ssrr.Password, "", req.RemoteAddr, req.UserAgent(), false, &ssrr.User, &deviceID) + return completeRegistration(req.Context(), userAPI, ssrr.User, ssrr.Password, "", req.RemoteAddr, req.UserAgent(), "", false, &ssrr.User, &deviceID) } From 5a0ec6e4435f5feda3a512fc565098739ace6b5d Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Tue, 15 Feb 2022 14:15:18 +0100 Subject: [PATCH 17/56] Add policy version to create-account & mediaapi --- cmd/create-account/main.go | 7 ++++++- mediaapi/routing/routing.go | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cmd/create-account/main.go b/cmd/create-account/main.go index 3ac0777052..9a79c50f25 100644 --- a/cmd/create-account/main.go +++ b/cmd/create-account/main.go @@ -81,7 +81,12 @@ func main() { logrus.Fatalln("Failed to connect to the database:", err.Error()) } - _, err = accountDB.CreateAccount(context.Background(), *username, pass, "") + policyVersion := "" + if cfg.Global.UserConsentOptions.Enabled() { + policyVersion = cfg.Global.UserConsentOptions.Version + } + + _, err = accountDB.CreateAccount(context.Background(), *username, pass, "", policyVersion) if err != nil { logrus.Fatalln("Failed to create the account:", err.Error()) } diff --git a/mediaapi/routing/routing.go b/mediaapi/routing/routing.go index 2476b5b205..6507505fe3 100644 --- a/mediaapi/routing/routing.go +++ b/mediaapi/routing/routing.go @@ -60,14 +60,14 @@ func Setup( PathToResult: map[string]*types.ThumbnailGenerationResult{}, } - uploadHandler := httputil.MakeAuthAPI("upload", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, dev *userapi.Device) util.JSONResponse { + uploadHandler := httputil.MakeAuthAPI("upload", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, dev *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } return Upload(req, cfg, dev, db, activeThumbnailGeneration) }) - configHandler := httputil.MakeAuthAPI("config", userAPI, cfg.Matrix.UserConsentOptions, func(req *http.Request, device *userapi.Device) util.JSONResponse { + configHandler := httputil.MakeAuthAPI("config", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } From 6482630f7b93f34ea3c32d05fc7665d6d35267aa Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Tue, 15 Feb 2022 14:28:51 +0100 Subject: [PATCH 18/56] Fix insert statement --- userapi/storage/accounts/postgres/accounts_table.go | 4 ++-- userapi/storage/accounts/sqlite3/accounts_table.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/userapi/storage/accounts/postgres/accounts_table.go b/userapi/storage/accounts/postgres/accounts_table.go index d79ed05352..a74d04ad43 100644 --- a/userapi/storage/accounts/postgres/accounts_table.go +++ b/userapi/storage/accounts/postgres/accounts_table.go @@ -120,9 +120,9 @@ func (s *accountsStatements) insertAccount( var err error if appserviceID == "" { - _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, nil, "") + _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, nil, policyVersion) } else { - _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID, policyVersion) + _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID, "") } if err != nil { return nil, err diff --git a/userapi/storage/accounts/sqlite3/accounts_table.go b/userapi/storage/accounts/sqlite3/accounts_table.go index f1c24e2208..95ebb022e1 100644 --- a/userapi/storage/accounts/sqlite3/accounts_table.go +++ b/userapi/storage/accounts/sqlite3/accounts_table.go @@ -120,9 +120,9 @@ func (s *accountsStatements) insertAccount( var err error if appserviceID == "" { - _, err = sqlutil.TxStmt(txn, stmt).ExecContext(ctx, localpart, createdTimeMS, hash, nil, "") + _, err = sqlutil.TxStmt(txn, stmt).ExecContext(ctx, localpart, createdTimeMS, hash, nil, policyVersion) } else { - _, err = sqlutil.TxStmt(txn, stmt).ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID, policyVersion) + _, err = sqlutil.TxStmt(txn, stmt).ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID, "") } if err != nil { return nil, err From 74da1f0fb3584437a163134f1e0edcbc41ff3867 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Wed, 16 Feb 2022 10:11:23 +0100 Subject: [PATCH 19/56] Remove "magic" Enabled function and use simple bool --- clientapi/routing/register.go | 4 ++-- clientapi/routing/routing.go | 2 +- cmd/create-account/main.go | 2 +- dendrite-config.yaml | 4 ++-- internal/httputil/httpapi.go | 2 +- setup/config/config_global.go | 9 ++++----- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/clientapi/routing/register.go b/clientapi/routing/register.go index 53f8110882..496c05b39c 100644 --- a/clientapi/routing/register.go +++ b/clientapi/routing/register.go @@ -699,7 +699,7 @@ func handleApplicationServiceRegistration( } policyVersion := "" - if cfg.Matrix.UserConsentOptions.Enabled() { + if cfg.Matrix.UserConsentOptions.Enabled { policyVersion = cfg.Matrix.UserConsentOptions.Version } @@ -725,7 +725,7 @@ func checkAndCompleteFlow( ) util.JSONResponse { if checkFlowCompleted(flow, cfg.Derived.Registration.Flows) { policyVersion := "" - if cfg.Matrix.UserConsentOptions.Enabled() { + if cfg.Matrix.UserConsentOptions.Enabled { policyVersion = cfg.Matrix.UserConsentOptions.Version } // This flow was completed, registration can continue diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index ea05604a8f..6eae02a3d8 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -118,7 +118,7 @@ func Setup( } // unspecced consent tracking - if cfg.Matrix.UserConsentOptions.Enabled() { + if cfg.Matrix.UserConsentOptions.Enabled { consentAPIMux.Handle("/consent", httputil.MakeHTMLAPI("consent", func(writer http.ResponseWriter, request *http.Request) *util.JSONResponse { return consent(writer, request, userAPI, cfg) diff --git a/cmd/create-account/main.go b/cmd/create-account/main.go index 9a79c50f25..d13ef31fa7 100644 --- a/cmd/create-account/main.go +++ b/cmd/create-account/main.go @@ -82,7 +82,7 @@ func main() { } policyVersion := "" - if cfg.Global.UserConsentOptions.Enabled() { + if cfg.Global.UserConsentOptions.Enabled { policyVersion = cfg.Global.UserConsentOptions.Version } diff --git a/dendrite-config.yaml b/dendrite-config.yaml index f6671b4523..250b76b5fb 100644 --- a/dendrite-config.yaml +++ b/dendrite-config.yaml @@ -69,9 +69,9 @@ global: disable_federation: false # Consent tracking configuration - # If either require_at_registration or send_server_notice_to_guest are true, consent - # messages will be sent to the users. user_consent: + # If the user consent tracking is enabled or not + enabled: false # Randomly generated string to be used to calculate the HMAC form_secret: "superSecretRandomlyGeneratedSecret" # Require consent when user registers for the first time diff --git a/internal/httputil/httpapi.go b/internal/httputil/httpapi.go index 89b43b3a6a..eaa3e349a3 100644 --- a/internal/httputil/httpapi.go +++ b/internal/httputil/httpapi.go @@ -87,7 +87,7 @@ func MakeAuthAPI( } }() - if userConsentCfg.Enabled() && requireConsent { + if userConsentCfg.Enabled && requireConsent { consentError := checkConsent(req.Context(), device.UserID, userAPI, userConsentCfg) if consentError != nil { return util.JSONResponse{ diff --git a/setup/config/config_global.go b/setup/config/config_global.go index d5ad9333e8..cada7bc9a7 100644 --- a/setup/config/config_global.go +++ b/setup/config/config_global.go @@ -213,6 +213,8 @@ func (c *DNSCacheOptions) Verify(configErrs *ConfigErrors, isMonolith bool) { // If either require_at_registration or send_server_notice_to_guest are true, consent // messages will be sent to the users. type UserConsentOptions struct { + // If consent tracking is enabled or not + Enabled bool `yaml:"enabled"` // Randomly generated string to be used to calculate the HMAC FormSecret string `yaml:"form_secret"` // Require consent when user registers for the first time @@ -241,6 +243,7 @@ type UserConsentOptions struct { } func (c *UserConsentOptions) Defaults(baseURL string) { + c.Enabled = false c.RequireAtRegistration = false c.SendServerNoticeToGuest = false c.PolicyName = "Privacy Policy" @@ -250,7 +253,7 @@ func (c *UserConsentOptions) Defaults(baseURL string) { } func (c *UserConsentOptions) Verify(configErrors *ConfigErrors, isMonolith bool) { - if c.Enabled() { + if c.Enabled { checkNotEmpty(configErrors, "template_dir", c.TemplateDir) checkNotEmpty(configErrors, "version", c.Version) checkNotEmpty(configErrors, "policy_name", c.PolicyName) @@ -281,7 +284,3 @@ func (c *UserConsentOptions) Verify(configErrors *ConfigErrors, isMonolith bool) } } } - -func (c *UserConsentOptions) Enabled() bool { - return c.RequireAtRegistration || c.SendServerNoticeToGuest -} From 2e6987f8bd7d6b7673fe14bab2b4b650f589571a Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 21 Feb 2022 12:12:07 +0100 Subject: [PATCH 20/56] Add missing files --- userapi/internal/api.go | 18 ++++-------------- userapi/storage/postgres/accounts_table.go | 6 +++--- userapi/storage/shared/storage.go | 14 ++++++++------ userapi/storage/sqlite3/accounts_table.go | 6 +++--- userapi/storage/tables/interface.go | 6 +++++- 5 files changed, 23 insertions(+), 27 deletions(-) diff --git a/userapi/internal/api.go b/userapi/internal/api.go index fdcf796fd7..57d8e09e07 100644 --- a/userapi/internal/api.go +++ b/userapi/internal/api.go @@ -57,17 +57,7 @@ func (a *UserInternalAPI) InputAccountData(ctx context.Context, req *api.InputAc } func (a *UserInternalAPI) PerformAccountCreation(ctx context.Context, req *api.PerformAccountCreationRequest, res *api.PerformAccountCreationResponse) error { - acc, err := a.DB.CreateAccount(ctx, req.Localpart, req.Password, req.AppServiceID, req.AccountType) - if req.AccountType == api.AccountTypeGuest { - acc, err := a.AccountDB.CreateGuestAccount(ctx) - if err != nil { - return err - } - res.AccountCreated = true - res.Account = acc - return nil - } - acc, err := a.AccountDB.CreateAccount(ctx, req.Localpart, req.Password, req.AppServiceID, req.PolicyVersion) + acc, err := a.DB.CreateAccount(ctx, req.Localpart, req.Password, req.AppServiceID, req.PolicyVersion, req.AccountType) if err != nil { if errors.Is(err, sqlutil.ErrUserExists) { // This account already exists switch req.OnConflict { @@ -612,7 +602,7 @@ func (a *UserInternalAPI) QueryPolicyVersion( res *api.QueryPolicyVersionResponse, ) error { var err error - res.PolicyVersion, err = a.AccountDB.GetPrivacyPolicy(ctx, req.LocalPart) + res.PolicyVersion, err = a.DB.GetPrivacyPolicy(ctx, req.LocalPart) if err != nil { return err } @@ -626,7 +616,7 @@ func (a *UserInternalAPI) GetOutdatedPolicy( res *api.QueryOutdatedPolicyUsersResponse, ) error { var err error - res.OutdatedUsers, err = a.AccountDB.GetOutdatedPolicy(ctx, req.PolicyVersion) + res.OutdatedUsers, err = a.DB.GetOutdatedPolicy(ctx, req.PolicyVersion) if err != nil { return err } @@ -639,5 +629,5 @@ func (a *UserInternalAPI) PerformUpdatePolicyVersion( req *api.UpdatePolicyVersionRequest, res *api.UpdatePolicyVersionResponse, ) error { - return a.AccountDB.UpdatePolicyVersion(ctx, req.PolicyVersion, req.LocalPart) + return a.DB.UpdatePolicyVersion(ctx, req.PolicyVersion, req.LocalPart) } diff --git a/userapi/storage/postgres/accounts_table.go b/userapi/storage/postgres/accounts_table.go index f276e20389..2eedee82f9 100644 --- a/userapi/storage/postgres/accounts_table.go +++ b/userapi/storage/postgres/accounts_table.go @@ -199,7 +199,7 @@ func (s *accountsStatements) SelectNewNumericLocalpart( } // selectPrivacyPolicy gets the current privacy policy a specific user accepted -func (s *accountsStatements) selectPrivacyPolicy( +func (s *accountsStatements) SelectPrivacyPolicy( ctx context.Context, txn *sql.Tx, localPart string, ) (policy string, err error) { stmt := s.selectPrivacyPolicyStmt @@ -211,7 +211,7 @@ func (s *accountsStatements) selectPrivacyPolicy( } // batchSelectPrivacyPolicy queries all users which didn't accept the current policy version -func (s *accountsStatements) batchSelectPrivacyPolicy( +func (s *accountsStatements) BatchSelectPrivacyPolicy( ctx context.Context, txn *sql.Tx, policyVersion string, ) (userIDs []string, err error) { stmt := s.batchSelectPrivacyPolicyStmt @@ -231,7 +231,7 @@ func (s *accountsStatements) batchSelectPrivacyPolicy( } // updatePolicyVersion sets the policy_version for a specific user -func (s *accountsStatements) updatePolicyVersion( +func (s *accountsStatements) UpdatePolicyVersion( ctx context.Context, txn *sql.Tx, policyVersion, localpart string, ) (err error) { stmt := s.updatePolicyVersionStmt diff --git a/userapi/storage/shared/storage.go b/userapi/storage/shared/storage.go index 1d48315cfc..c28505f154 100644 --- a/userapi/storage/shared/storage.go +++ b/userapi/storage/shared/storage.go @@ -16,7 +16,9 @@ package shared import ( "context" + "crypto/rand" "database/sql" + "encoding/base64" "encoding/json" "errors" "fmt" @@ -671,8 +673,8 @@ func (d *Database) GetLoginTokenDataByToken(ctx context.Context, token string) ( // GetPrivacyPolicy returns the accepted privacy policy version, if any. func (d *Database) GetPrivacyPolicy(ctx context.Context, localpart string) (policyVersion string, err error) { - err = d.writer.Do(d.db, nil, func(txn *sql.Tx) error { - policyVersion, err = d.Accounts.selectPrivacyPolicy(ctx, txn, localpart) + err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { + policyVersion, err = d.Accounts.SelectPrivacyPolicy(ctx, txn, localpart) return err }) return @@ -680,8 +682,8 @@ func (d *Database) GetPrivacyPolicy(ctx context.Context, localpart string) (poli // GetOutdatedPolicy queries all users which didn't accept the current policy version func (d *Database) GetOutdatedPolicy(ctx context.Context, policyVersion string) (userIDs []string, err error) { - err = d.writer.Do(d.db, nil, func(txn *sql.Tx) error { - userIDs, err = d.accounts.batchSelectPrivacyPolicy(ctx, txn, policyVersion) + err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { + userIDs, err = d.Accounts.BatchSelectPrivacyPolicy(ctx, txn, policyVersion) return err }) return @@ -689,8 +691,8 @@ func (d *Database) GetOutdatedPolicy(ctx context.Context, policyVersion string) // UpdatePolicyVersion sets the accepted policy_version for a user. func (d *Database) UpdatePolicyVersion(ctx context.Context, policyVersion, localpart string) (err error) { - err = d.writer.Do(d.db, nil, func(txn *sql.Tx) error { - return d.accounts.updatePolicyVersion(ctx, txn, policyVersion, localpart) + err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { + return d.Accounts.UpdatePolicyVersion(ctx, txn, policyVersion, localpart) }) return } diff --git a/userapi/storage/sqlite3/accounts_table.go b/userapi/storage/sqlite3/accounts_table.go index 483736a807..0bf54ee185 100644 --- a/userapi/storage/sqlite3/accounts_table.go +++ b/userapi/storage/sqlite3/accounts_table.go @@ -199,7 +199,7 @@ func (s *accountsStatements) SelectNewNumericLocalpart( // selectPrivacyPolicy gets the current privacy policy a specific user accepted -func (s *accountsStatements) selectPrivacyPolicy( +func (s *accountsStatements) SelectPrivacyPolicy( ctx context.Context, txn *sql.Tx, localPart string, ) (policy string, err error) { stmt := s.selectPrivacyPolicyStmt @@ -211,7 +211,7 @@ func (s *accountsStatements) selectPrivacyPolicy( } // batchSelectPrivacyPolicy queries all users which didn't accept the current policy version -func (s *accountsStatements) batchSelectPrivacyPolicy( +func (s *accountsStatements) BatchSelectPrivacyPolicy( ctx context.Context, txn *sql.Tx, policyVersion string, ) (userIDs []string, err error) { stmt := s.batchSelectPrivacyPolicyStmt @@ -231,7 +231,7 @@ func (s *accountsStatements) batchSelectPrivacyPolicy( } // updatePolicyVersion sets the policy_version for a specific user -func (s *accountsStatements) updatePolicyVersion( +func (s *accountsStatements) UpdatePolicyVersion( ctx context.Context, txn *sql.Tx, policyVersion, localpart string, ) (err error) { stmt := s.updatePolicyVersionStmt diff --git a/userapi/storage/tables/interface.go b/userapi/storage/tables/interface.go index 12939ced54..59f96d0b5f 100644 --- a/userapi/storage/tables/interface.go +++ b/userapi/storage/tables/interface.go @@ -30,12 +30,16 @@ type AccountDataTable interface { } type AccountsTable interface { - InsertAccount(ctx context.Context, txn *sql.Tx, localpart, hash, appserviceID string, accountType api.AccountType) (*api.Account, error) + InsertAccount(ctx context.Context, txn *sql.Tx, localpart, hash, appserviceID, policyVersion string, accountType api.AccountType) (*api.Account, error) UpdatePassword(ctx context.Context, localpart, passwordHash string) (err error) DeactivateAccount(ctx context.Context, localpart string) (err error) SelectPasswordHash(ctx context.Context, localpart string) (hash string, err error) SelectAccountByLocalpart(ctx context.Context, localpart string) (*api.Account, error) SelectNewNumericLocalpart(ctx context.Context, txn *sql.Tx) (id int64, err error) + + SelectPrivacyPolicy(ctx context.Context, txn *sql.Tx, localPart string) (policy string, err error) + BatchSelectPrivacyPolicy(ctx context.Context, txn *sql.Tx, policyVersion string) (userIDs []string, err error) + UpdatePolicyVersion(ctx context.Context, txn *sql.Tx, policyVersion, localpart string) (err error) } type DevicesTable interface { From cb4526793d84bad0fa7fef0b2a94350fe58e86a3 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 21 Feb 2022 12:15:56 +0100 Subject: [PATCH 21/56] Add missing migrations --- userapi/storage/postgres/storage.go | 1 + .../sqlite3/deltas/2022021012490600_add_account_type.go | 6 ------ userapi/storage/sqlite3/storage.go | 1 + 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/userapi/storage/postgres/storage.go b/userapi/storage/postgres/storage.go index ac5c59b817..28b43a01d6 100644 --- a/userapi/storage/postgres/storage.go +++ b/userapi/storage/postgres/storage.go @@ -45,6 +45,7 @@ func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserver deltas.LoadIsActive(m) //deltas.LoadLastSeenTSIP(m) deltas.LoadAddAccountType(m) + deltas.LoadAddPolicyVersion(m) if err = m.RunDeltas(db, dbProperties); err != nil { return nil, err } diff --git a/userapi/storage/sqlite3/deltas/2022021012490600_add_account_type.go b/userapi/storage/sqlite3/deltas/2022021012490600_add_account_type.go index 9b058dedd9..bcced55fd8 100644 --- a/userapi/storage/sqlite3/deltas/2022021012490600_add_account_type.go +++ b/userapi/storage/sqlite3/deltas/2022021012490600_add_account_type.go @@ -4,15 +4,9 @@ import ( "database/sql" "fmt" - "github.com/pressly/goose" - "github.com/matrix-org/dendrite/internal/sqlutil" ) -func init() { - goose.AddMigration(UpAddAccountType, DownAddAccountType) -} - func LoadAddAccountType(m *sqlutil.Migrations) { m.AddMigration(UpAddAccountType, DownAddAccountType) } diff --git a/userapi/storage/sqlite3/storage.go b/userapi/storage/sqlite3/storage.go index 98c2449773..44a79c94e5 100644 --- a/userapi/storage/sqlite3/storage.go +++ b/userapi/storage/sqlite3/storage.go @@ -46,6 +46,7 @@ func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserver deltas.LoadIsActive(m) //deltas.LoadLastSeenTSIP(m) deltas.LoadAddAccountType(m) + deltas.LoadAddPolicyVersion(m) if err = m.RunDeltas(db, dbProperties); err != nil { return nil, err } From fb95331aa24b6e9e01c15288ab8a867e0659fde7 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 21 Feb 2022 14:26:00 +0100 Subject: [PATCH 22/56] Add posibility to track sent policy versions --- userapi/api/api.go | 1 + userapi/internal/api.go | 2 +- userapi/storage/interface.go | 2 +- userapi/storage/postgres/accounts_table.go | 36 ++++++++++------ .../2022021414375800_add_policy_version.go | 6 ++- userapi/storage/shared/storage.go | 4 +- userapi/storage/sqlite3/accounts_table.go | 43 ++++++++++++------- .../2022021414375800_add_policy_version.go | 6 ++- userapi/storage/tables/interface.go | 2 +- 9 files changed, 65 insertions(+), 37 deletions(-) diff --git a/userapi/api/api.go b/userapi/api/api.go index 5140cc5b83..a402753071 100644 --- a/userapi/api/api.go +++ b/userapi/api/api.go @@ -362,6 +362,7 @@ type QueryOutdatedPolicyUsersResponse struct { // UpdatePolicyVersionRequest is the request for UpdatePolicyVersionRequest type UpdatePolicyVersionRequest struct { PolicyVersion, LocalPart string + ServerNoticeUpdate bool } // UpdatePolicyVersionResponse is the response for UpdatePolicyVersionRequest diff --git a/userapi/internal/api.go b/userapi/internal/api.go index 57d8e09e07..862dd0c2c4 100644 --- a/userapi/internal/api.go +++ b/userapi/internal/api.go @@ -629,5 +629,5 @@ func (a *UserInternalAPI) PerformUpdatePolicyVersion( req *api.UpdatePolicyVersionRequest, res *api.UpdatePolicyVersionResponse, ) error { - return a.DB.UpdatePolicyVersion(ctx, req.PolicyVersion, req.LocalPart) + return a.DB.UpdatePolicyVersion(ctx, req.PolicyVersion, req.LocalPart, req.ServerNoticeUpdate) } diff --git a/userapi/storage/interface.go b/userapi/storage/interface.go index 9e4ff4b4b3..39164fed00 100644 --- a/userapi/storage/interface.go +++ b/userapi/storage/interface.go @@ -53,7 +53,7 @@ type Database interface { GetOpenIDTokenAttributes(ctx context.Context, token string) (*api.OpenIDTokenAttributes, error) GetPrivacyPolicy(ctx context.Context, localpart string) (policyVersion string, err error) GetOutdatedPolicy(ctx context.Context, policyVersion string) (userIDs []string, err error) - UpdatePolicyVersion(ctx context.Context, policyVersion, localpart string) error + UpdatePolicyVersion(ctx context.Context, policyVersion, localpart string, serverNotice bool) error // Key backups CreateKeyBackup(ctx context.Context, userID, algorithm string, authData json.RawMessage) (version string, err error) diff --git a/userapi/storage/postgres/accounts_table.go b/userapi/storage/postgres/accounts_table.go index 2eedee82f9..22ef832746 100644 --- a/userapi/storage/postgres/accounts_table.go +++ b/userapi/storage/postgres/accounts_table.go @@ -45,7 +45,9 @@ CREATE TABLE IF NOT EXISTS account_accounts ( -- The account_type (user = 1, guest = 2, admin = 3, appservice = 4) account_type SMALLINT NOT NULL, -- The policy version this user has accepted - policy_version TEXT + policy_version TEXT, + -- The policy version the user received from the server notices room + policy_version_sent TEXT -- TODO: -- upgraded_ts, devices, any email reset stuff? ); @@ -75,22 +77,26 @@ const selectPrivacyPolicySQL = "" + "SELECT policy_version FROM account_accounts WHERE localpart = $1" const batchSelectPrivacyPolicySQL = "" + - "SELECT localpart FROM account_accounts WHERE policy_version IS NULL or policy_version <> $1" + "SELECT localpart FROM account_accounts WHERE (policy_version IS NULL OR policy_version <> $1) AND (policy_version_sent IS NULL OR policy_version_sent <> $1)" const updatePolicyVersionSQL = "" + "UPDATE account_accounts SET policy_version = $1 WHERE localpart = $2" +const updatePolicyVersionServerNoticeSQL = "" + + "UPDATE account_accounts SET policy_version_sent = $1 WHERE localpart = $2" + type accountsStatements struct { - insertAccountStmt *sql.Stmt - updatePasswordStmt *sql.Stmt - deactivateAccountStmt *sql.Stmt - selectAccountByLocalpartStmt *sql.Stmt - selectPasswordHashStmt *sql.Stmt - selectNewNumericLocalpartStmt *sql.Stmt - selectPrivacyPolicyStmt *sql.Stmt - batchSelectPrivacyPolicyStmt *sql.Stmt - updatePolicyVersionStmt *sql.Stmt - serverName gomatrixserverlib.ServerName + insertAccountStmt *sql.Stmt + updatePasswordStmt *sql.Stmt + deactivateAccountStmt *sql.Stmt + selectAccountByLocalpartStmt *sql.Stmt + selectPasswordHashStmt *sql.Stmt + selectNewNumericLocalpartStmt *sql.Stmt + selectPrivacyPolicyStmt *sql.Stmt + batchSelectPrivacyPolicyStmt *sql.Stmt + updatePolicyVersionStmt *sql.Stmt + updatePolicyVersionServerNoticeStmt *sql.Stmt + serverName gomatrixserverlib.ServerName } func NewPostgresAccountsTable(db *sql.DB, serverName gomatrixserverlib.ServerName) (tables.AccountsTable, error) { @@ -111,6 +117,7 @@ func NewPostgresAccountsTable(db *sql.DB, serverName gomatrixserverlib.ServerNam {&s.selectPrivacyPolicyStmt, selectPrivacyPolicySQL}, {&s.batchSelectPrivacyPolicyStmt, batchSelectPrivacyPolicySQL}, {&s.updatePolicyVersionStmt, updatePolicyVersionSQL}, + {&s.updatePolicyVersionServerNoticeStmt, updatePolicyVersionServerNoticeSQL}, }.Prepare(db) } @@ -232,9 +239,12 @@ func (s *accountsStatements) BatchSelectPrivacyPolicy( // updatePolicyVersion sets the policy_version for a specific user func (s *accountsStatements) UpdatePolicyVersion( - ctx context.Context, txn *sql.Tx, policyVersion, localpart string, + ctx context.Context, txn *sql.Tx, policyVersion, localpart string, serverNotice bool, ) (err error) { stmt := s.updatePolicyVersionStmt + if serverNotice { + stmt = s.updatePolicyVersionServerNoticeStmt + } if txn != nil { stmt = sqlutil.TxStmt(txn, stmt) } diff --git a/userapi/storage/postgres/deltas/2022021414375800_add_policy_version.go b/userapi/storage/postgres/deltas/2022021414375800_add_policy_version.go index 8cbbe6f330..10b1006df7 100644 --- a/userapi/storage/postgres/deltas/2022021414375800_add_policy_version.go +++ b/userapi/storage/postgres/deltas/2022021414375800_add_policy_version.go @@ -12,7 +12,8 @@ func LoadAddPolicyVersion(m *sqlutil.Migrations) { } func UpAddPolicyVersion(tx *sql.Tx) error { - _, err := tx.Exec("ALTER TABLE account_accounts ADD COLUMN IF NOT EXISTS policy_version TEXT;") + _, err := tx.Exec("ALTER TABLE account_accounts ADD COLUMN IF NOT EXISTS policy_version TEXT;" + + "ALTER TABLE account_accounts ADD COLUMN IF NOT EXISTS policy_version_sent TEXT;") if err != nil { return fmt.Errorf("failed to execute upgrade: %w", err) } @@ -20,7 +21,8 @@ func UpAddPolicyVersion(tx *sql.Tx) error { } func DownAddPolicyVersion(tx *sql.Tx) error { - _, err := tx.Exec("ALTER TABLE account_accounts DROP COLUMN policy_version;") + _, err := tx.Exec("ALTER TABLE account_accounts DROP COLUMN policy_version;" + + "ALTER TABLE account_accounts DROP COLUMN policy_version_sent;") if err != nil { return fmt.Errorf("failed to execute downgrade: %w", err) } diff --git a/userapi/storage/shared/storage.go b/userapi/storage/shared/storage.go index c28505f154..085a4e2079 100644 --- a/userapi/storage/shared/storage.go +++ b/userapi/storage/shared/storage.go @@ -690,9 +690,9 @@ func (d *Database) GetOutdatedPolicy(ctx context.Context, policyVersion string) } // UpdatePolicyVersion sets the accepted policy_version for a user. -func (d *Database) UpdatePolicyVersion(ctx context.Context, policyVersion, localpart string) (err error) { +func (d *Database) UpdatePolicyVersion(ctx context.Context, policyVersion, localpart string, serverNotice bool) (err error) { err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { - return d.Accounts.UpdatePolicyVersion(ctx, txn, policyVersion, localpart) + return d.Accounts.UpdatePolicyVersion(ctx, txn, policyVersion, localpart, serverNotice) }) return } diff --git a/userapi/storage/sqlite3/accounts_table.go b/userapi/storage/sqlite3/accounts_table.go index 0bf54ee185..cbd073d514 100644 --- a/userapi/storage/sqlite3/accounts_table.go +++ b/userapi/storage/sqlite3/accounts_table.go @@ -45,7 +45,9 @@ CREATE TABLE IF NOT EXISTS account_accounts ( -- The account_type (user = 1, guest = 2, admin = 3, appservice = 4) account_type INTEGER NOT NULL, -- The policy version this user has accepted - policy_version TEXT + policy_version TEXT, + -- The policy version the user received from the server notices room + policy_version_sent TEXT -- TODO: -- upgraded_ts, devices, any email reset stuff? ); @@ -73,23 +75,27 @@ const selectPrivacyPolicySQL = "" + "SELECT policy_version FROM account_accounts WHERE localpart = $1" const batchSelectPrivacyPolicySQL = "" + - "SELECT localpart FROM account_accounts WHERE policy_version IS NULL or policy_version <> $1" + "SELECT localpart FROM account_accounts WHERE (policy_version IS NULL OR policy_version <> $1) AND (policy_version_sent IS NULL OR policy_version_sent <> $2)" const updatePolicyVersionSQL = "" + "UPDATE account_accounts SET policy_version = $1 WHERE localpart = $2" +const updatePolicyVersionServerNoticeSQL = "" + + "UPDATE account_accounts SET policy_version_sent = $1 WHERE localpart = $2" + type accountsStatements struct { - db *sql.DB - insertAccountStmt *sql.Stmt - updatePasswordStmt *sql.Stmt - deactivateAccountStmt *sql.Stmt - selectAccountByLocalpartStmt *sql.Stmt - selectPasswordHashStmt *sql.Stmt - selectNewNumericLocalpartStmt *sql.Stmt - selectPrivacyPolicyStmt *sql.Stmt - batchSelectPrivacyPolicyStmt *sql.Stmt - updatePolicyVersionStmt *sql.Stmt - serverName gomatrixserverlib.ServerName + db *sql.DB + insertAccountStmt *sql.Stmt + updatePasswordStmt *sql.Stmt + deactivateAccountStmt *sql.Stmt + selectAccountByLocalpartStmt *sql.Stmt + selectPasswordHashStmt *sql.Stmt + selectNewNumericLocalpartStmt *sql.Stmt + selectPrivacyPolicyStmt *sql.Stmt + batchSelectPrivacyPolicyStmt *sql.Stmt + updatePolicyVersionStmt *sql.Stmt + updatePolicyVersionServerNoticeStmt *sql.Stmt + serverName gomatrixserverlib.ServerName } func NewSQLiteAccountsTable(db *sql.DB, serverName gomatrixserverlib.ServerName) (tables.AccountsTable, error) { @@ -111,6 +117,7 @@ func NewSQLiteAccountsTable(db *sql.DB, serverName gomatrixserverlib.ServerName) {&s.selectPrivacyPolicyStmt, selectPrivacyPolicySQL}, {&s.batchSelectPrivacyPolicyStmt, batchSelectPrivacyPolicySQL}, {&s.updatePolicyVersionStmt, updatePolicyVersionSQL}, + {&s.updatePolicyVersionServerNoticeStmt, updatePolicyVersionServerNoticeSQL}, }.Prepare(db) } @@ -218,7 +225,10 @@ func (s *accountsStatements) BatchSelectPrivacyPolicy( if txn != nil { stmt = sqlutil.TxStmt(txn, stmt) } - rows, err := stmt.QueryContext(ctx, policyVersion) + rows, err := stmt.QueryContext(ctx, policyVersion, policyVersion) + if err != nil { + return nil, err + } defer rows.Close() for rows.Next() { var userID string @@ -232,9 +242,12 @@ func (s *accountsStatements) BatchSelectPrivacyPolicy( // updatePolicyVersion sets the policy_version for a specific user func (s *accountsStatements) UpdatePolicyVersion( - ctx context.Context, txn *sql.Tx, policyVersion, localpart string, + ctx context.Context, txn *sql.Tx, policyVersion, localpart string, serverNotice bool, ) (err error) { stmt := s.updatePolicyVersionStmt + if serverNotice { + stmt = s.updatePolicyVersionServerNoticeStmt + } if txn != nil { stmt = sqlutil.TxStmt(txn, stmt) } diff --git a/userapi/storage/sqlite3/deltas/2022021414375800_add_policy_version.go b/userapi/storage/sqlite3/deltas/2022021414375800_add_policy_version.go index ae69cf0f01..2292b90319 100644 --- a/userapi/storage/sqlite3/deltas/2022021414375800_add_policy_version.go +++ b/userapi/storage/sqlite3/deltas/2022021414375800_add_policy_version.go @@ -12,7 +12,8 @@ func LoadAddPolicyVersion(m *sqlutil.Migrations) { } func UpAddPolicyVersion(tx *sql.Tx) error { - _, err := tx.Exec("ALTER TABLE account_accounts ADD COLUMN policy_version TEXT;") + _, err := tx.Exec("ALTER TABLE account_accounts ADD COLUMN policy_version TEXT;" + + "ALTER TABLE account_accounts ADD COLUMN policy_version_sent TEXT;") if err != nil { return fmt.Errorf("failed to execute upgrade: %w", err) } @@ -20,7 +21,8 @@ func UpAddPolicyVersion(tx *sql.Tx) error { } func DownAddPolicyVersion(tx *sql.Tx) error { - _, err := tx.Exec("ALTER TABLE account_accounts DROP COLUMN policy_version;") + _, err := tx.Exec("ALTER TABLE account_accounts DROP COLUMN policy_version;" + + "ALTER TABLE account_accounts DROP COLUMN policy_version_sent;") if err != nil { return fmt.Errorf("failed to execute downgrade: %w", err) } diff --git a/userapi/storage/tables/interface.go b/userapi/storage/tables/interface.go index 59f96d0b5f..a126086b14 100644 --- a/userapi/storage/tables/interface.go +++ b/userapi/storage/tables/interface.go @@ -39,7 +39,7 @@ type AccountsTable interface { SelectPrivacyPolicy(ctx context.Context, txn *sql.Tx, localPart string) (policy string, err error) BatchSelectPrivacyPolicy(ctx context.Context, txn *sql.Tx, policyVersion string) (userIDs []string, err error) - UpdatePolicyVersion(ctx context.Context, txn *sql.Tx, policyVersion, localpart string) (err error) + UpdatePolicyVersion(ctx context.Context, txn *sql.Tx, policyVersion, localpart string, serverNotice bool) (err error) } type DevicesTable interface { From 219a15c4c3390852c9f5aee47c1fff4fb64e5f33 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 21 Feb 2022 14:27:13 +0100 Subject: [PATCH 23/56] Load templates into one variable --- internal/httputil/httpapi.go | 2 +- setup/config/config.go | 4 ++-- setup/config/config_global.go | 9 +++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/internal/httputil/httpapi.go b/internal/httputil/httpapi.go index eaa3e349a3..d8c09d9cc2 100644 --- a/internal/httputil/httpapi.go +++ b/internal/httputil/httpapi.go @@ -134,7 +134,7 @@ func checkConsent(ctx context.Context, userID string, userAPI userapi.UserIntern }{ ConsentURL: uri, } - if err = userConsentCfg.BlockEventsTemplate.ExecuteTemplate(msg, "blockEventsError", c); err != nil { + if err = userConsentCfg.TextTemplates.ExecuteTemplate(msg, "blockEventsError", c); err != nil { logrus.Infof("error consent message: %+v", err) return jsonerror.Unknown("unable to get consent URL") } diff --git a/setup/config/config.go b/setup/config/config.go index 90eef3320a..8a15240aa9 100644 --- a/setup/config/config.go +++ b/setup/config/config.go @@ -288,7 +288,7 @@ func (config *Dendrite) Derive() error { // TODO: Add email auth type // TODO: Add MSISDN auth type - if config.Global.UserConsentOptions.RequireAtRegistration { + if config.Global.UserConsentOptions.Enabled && config.Global.UserConsentOptions.RequireAtRegistration { uri := config.Global.BaseURL + "/_matrix/consent?v=" + config.Global.UserConsentOptions.Version config.Derived.Registration.Params[authtypes.LoginTypeTerms] = Terms{ Policies: Policies{ @@ -314,7 +314,7 @@ func (config *Dendrite) Derive() error { // prepend each flow with LoginTypeTerms or LoginTypeRecaptcha for i, flow := range config.Derived.Registration.Flows { - if config.Global.UserConsentOptions.RequireAtRegistration { + if config.Global.UserConsentOptions.Enabled && config.Global.UserConsentOptions.RequireAtRegistration { flow.Stages = append([]authtypes.LoginType{authtypes.LoginTypeTerms}, flow.Stages...) } if config.ClientAPI.RecaptchaEnabled { diff --git a/setup/config/config_global.go b/setup/config/config_global.go index 5c81ddcb2a..22be162a49 100644 --- a/setup/config/config_global.go +++ b/setup/config/config_global.go @@ -266,9 +266,9 @@ type UserConsentOptions struct { // The error message to display if the user hasn't given their consent yet BlockEventsError string `yaml:"block_events_error"` // All loaded templates - Templates *template.Template `yaml:"-"` - BlockEventsTemplate *textTemplate.Template `yaml:"-"` - // + Templates *template.Template `yaml:"-"` + TextTemplates *textTemplate.Template `yaml:"-"` + // The BaseURL, used for building the consent URL BaseURL string `yaml:"-"` } @@ -298,7 +298,8 @@ func (c *UserConsentOptions) Verify(configErrors *ConfigErrors, isMonolith bool) return } - c.BlockEventsTemplate = textTemplate.Must(textTemplate.New("blockEventsError").Parse(c.BlockEventsError)) + c.TextTemplates = textTemplate.Must(textTemplate.New("blockEventsError").Parse(c.BlockEventsError)) + c.TextTemplates = textTemplate.Must(c.TextTemplates.New("serverNoticeTemplate").Parse(c.ServerNoticeContent.Body)) // Read all defined *.gohtml templates t, err := template.ParseGlob(filepath.Join(p, "*.gohtml")) From 6622fda08cd01b1676d5888e2f84e0354e222cbe Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 21 Feb 2022 14:27:59 +0100 Subject: [PATCH 24/56] Add sending server notices on startup --- clientapi/routing/consent_tracking.go | 86 ++++++++++++++++++++ clientapi/routing/room_tagging.go | 9 ++- clientapi/routing/routing.go | 12 ++- clientapi/routing/server_notices.go | 111 ++++++++++++++++---------- 4 files changed, 170 insertions(+), 48 deletions(-) diff --git a/clientapi/routing/consent_tracking.go b/clientapi/routing/consent_tracking.go index 2ab739067c..ca33da08c8 100644 --- a/clientapi/routing/consent_tracking.go +++ b/clientapi/routing/consent_tracking.go @@ -1,14 +1,20 @@ package routing import ( + "bytes" + "context" "crypto/hmac" "crypto/sha256" "encoding/hex" + "fmt" "net/http" + appserviceAPI "github.com/matrix-org/dendrite/appservice/api" "github.com/matrix-org/dendrite/clientapi/jsonerror" + "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/setup/config" userapi "github.com/matrix-org/dendrite/userapi/api" + userdb "github.com/matrix-org/dendrite/userapi/storage" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" "github.com/sirupsen/logrus" @@ -104,6 +110,86 @@ func consent(writer http.ResponseWriter, req *http.Request, userAPI userapi.User return &util.JSONResponse{Code: http.StatusOK} } +func sendServerNoticeForConsent(userAPI userapi.UserInternalAPI, rsAPI api.RoomserverInternalAPI, + cfgNotices *config.ServerNotices, + cfgClient *config.ClientAPI, + senderDevice *userapi.Device, + accountsDB userdb.Database, + asAPI appserviceAPI.AppServiceQueryAPI, +) { + logrus.Infof("Sending server notice to users who have not yet accepted the policy") + res := &userapi.QueryOutdatedPolicyUsersResponse{} + if err := userAPI.GetOutdatedPolicy(context.Background(), &userapi.QueryOutdatedPolicyUsersRequest{ + PolicyVersion: cfgClient.Matrix.UserConsentOptions.Version, + }, res); err != nil { + logrus.WithError(err).Error("unable to fetch users with outdated consent policy") + return + } + + consentOpts := cfgClient.Matrix.UserConsentOptions + data := make(map[string]string) + var err error + sentMessages := 0 + for _, userID := range res.OutdatedUsers { + if userID == cfgClient.Matrix.ServerNotices.LocalPart { + continue + } + userID = fmt.Sprintf("@%s:%s", userID, cfgClient.Matrix.ServerName) + data["ConsentURL"], err = buildConsentURI(cfgClient, userID) + if err != nil { + logrus.WithError(err).WithField("userID", userID).Error("unable to construct consentURI") + continue + } + logrus.Debugf("sending message to %s", userID) + msgBody := &bytes.Buffer{} + + if err = consentOpts.TextTemplates.ExecuteTemplate(msgBody, "serverNoticeTemplate", data); err != nil { + logrus.WithError(err).WithField("userID", userID).Error("unable to execute serverNoticeTemplate") + continue + } + + req := sendServerNoticeRequest{ + UserID: userID, + Content: struct { + MsgType string `json:"msgtype,omitempty"` + Body string `json:"body,omitempty"` + }{ + MsgType: consentOpts.ServerNoticeContent.MsgType, + Body: msgBody.String(), + }, + } + _, err = sendServerNotice(context.Background(), req, rsAPI, cfgNotices, cfgClient, senderDevice, accountsDB, asAPI, userAPI, nil, nil, nil) + if err != nil { + logrus.WithError(err).WithField("userID", userID).Error("failed to send server notice for consent to user") + continue + } + sentMessages++ + res := &userapi.UpdatePolicyVersionResponse{} + if err = userAPI.PerformUpdatePolicyVersion(context.Background(), &userapi.UpdatePolicyVersionRequest{ + PolicyVersion: consentOpts.Version, + LocalPart: userID, + ServerNoticeUpdate: true, + }, res); err != nil { + logrus.WithError(err).WithField("userID", userID).Error("failed to update policy version") + continue + } + } + logrus.Infof("Send messages to %d users", sentMessages) +} + +func buildConsentURI(cfgClient *config.ClientAPI, userID string) (string, error) { + consentOpts := cfgClient.Matrix.UserConsentOptions + + mac := hmac.New(sha256.New, []byte(consentOpts.FormSecret)) + _, err := mac.Write([]byte(userID)) + if err != nil { + return "", err + } + userMAC := mac.Sum(nil) + + return fmt.Sprintf("%s/_matrix/consent?u=%s&h=%s&v=%s", consentOpts.BaseURL, userID, userMAC, consentOpts.Version), nil +} + func validHMAC(username, userHMAC, secret string) (bool, error) { mac := hmac.New(sha256.New, []byte(secret)) _, err := mac.Write([]byte(username)) diff --git a/clientapi/routing/room_tagging.go b/clientapi/routing/room_tagging.go index c683cc9495..85effcddce 100644 --- a/clientapi/routing/room_tagging.go +++ b/clientapi/routing/room_tagging.go @@ -15,6 +15,7 @@ package routing import ( + "context" "encoding/json" "net/http" @@ -93,7 +94,7 @@ func PutTag( } tagContent.Tags[tag] = properties - if err = saveTagData(req, userID, roomID, userAPI, tagContent); err != nil { + if err = saveTagData(req.Context(), userID, roomID, userAPI, tagContent); err != nil { util.GetLogger(req.Context()).WithError(err).Error("saveTagData failed") return jsonerror.InternalServerError() } @@ -145,7 +146,7 @@ func DeleteTag( } } - if err = saveTagData(req, userID, roomID, userAPI, tagContent); err != nil { + if err = saveTagData(req.Context(), userID, roomID, userAPI, tagContent); err != nil { util.GetLogger(req.Context()).WithError(err).Error("saveTagData failed") return jsonerror.InternalServerError() } @@ -191,7 +192,7 @@ func obtainSavedTags( // saveTagData saves the provided tag data into the database func saveTagData( - req *http.Request, + context context.Context, userID string, roomID string, userAPI api.UserInternalAPI, @@ -208,5 +209,5 @@ func saveTagData( AccountData: json.RawMessage(newTagData), } dataRes := api.InputAccountDataResponse{} - return userAPI.InputAccountData(req.Context(), &dataReq, &dataRes) + return userAPI.InputAccountData(context, &dataReq, &dataRes) } diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index 230053f600..0fa4885bf2 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -119,9 +119,13 @@ func Setup( } // server notifications + var ( + serverNotificationSender *userapi.Device + err error + ) if cfg.Matrix.ServerNotices.Enabled { logrus.Info("Enabling server notices at /_synapse/admin/v1/send_server_notice") - serverNotificationSender, err := getSenderDevice(context.Background(), userAPI, accountDB, cfg) + serverNotificationSender, err = getSenderDevice(context.Background(), userAPI, accountDB, cfg) if err != nil { logrus.WithError(err).Fatal("unable to get account for sending sending server notices") } @@ -172,6 +176,12 @@ func Setup( // unspecced consent tracking if cfg.Matrix.UserConsentOptions.Enabled { + if !cfg.Matrix.ServerNotices.Enabled { + logrus.Warnf("Consent tracking is enabled, but server notes are not. No server notice will be sent to users") + } else { + // start a new go routine to send messages about consent + go sendServerNoticeForConsent(userAPI, rsAPI, &cfg.Matrix.ServerNotices, cfg, serverNotificationSender, accountDB, asAPI) + } consentAPIMux.Handle("/consent", httputil.MakeHTMLAPI("consent", func(writer http.ResponseWriter, request *http.Request) *util.JSONResponse { return consent(writer, request, userAPI, cfg) diff --git a/clientapi/routing/server_notices.go b/clientapi/routing/server_notices.go index 42a303a6b9..40e42d72a3 100644 --- a/clientapi/routing/server_notices.go +++ b/clientapi/routing/server_notices.go @@ -85,41 +85,38 @@ func SendServerNotice( if resErr != nil { return *resErr } + res, _ := sendServerNotice(ctx, r, rsAPI, cfgNotices, cfgClient, senderDevice, accountsDB, asAPI, userAPI, txnID, device, txnCache) + return res +} + +func sendServerNotice( + ctx context.Context, + serverNoticeRequest sendServerNoticeRequest, + rsAPI api.RoomserverInternalAPI, + cfgNotices *config.ServerNotices, + cfgClient *config.ClientAPI, + senderDevice *userapi.Device, + accountsDB userdb.Database, + asAPI appserviceAPI.AppServiceQueryAPI, + userAPI userapi.UserInternalAPI, + txnID *string, + device *userapi.Device, + txnCache *transactions.Cache, +) (util.JSONResponse, error) { // check that all required fields are set - if !r.valid() { + if !serverNoticeRequest.valid() { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: jsonerror.BadJSON("Invalid request"), - } + }, nil } // get rooms for specified user - allUserRooms := []string{} - userRooms := api.QueryRoomsForUserResponse{} - if err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{ - UserID: r.UserID, - WantMembership: "join", - }, &userRooms); err != nil { - return util.ErrorResponse(err) - } - allUserRooms = append(allUserRooms, userRooms.RoomIDs...) - // get invites for specified user - if err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{ - UserID: r.UserID, - WantMembership: "invite", - }, &userRooms); err != nil { - return util.ErrorResponse(err) - } - allUserRooms = append(allUserRooms, userRooms.RoomIDs...) - // get left rooms for specified user - if err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{ - UserID: r.UserID, - WantMembership: "leave", - }, &userRooms); err != nil { - return util.ErrorResponse(err) + allUserRooms, err := getAllUserRooms(ctx, rsAPI, serverNoticeRequest.UserID) + if err != nil { + return util.ErrorResponse(err), nil } - allUserRooms = append(allUserRooms, userRooms.RoomIDs...) // get rooms of the sender senderUserID := fmt.Sprintf("@%s:%s", cfgNotices.LocalPart, cfgClient.Matrix.ServerName) @@ -128,7 +125,7 @@ func SendServerNotice( UserID: senderUserID, WantMembership: "join", }, &senderRooms); err != nil { - return util.ErrorResponse(err) + return util.ErrorResponse(err), nil } // check if we have rooms in common @@ -142,7 +139,7 @@ func SendServerNotice( } if len(commonRooms) > 1 { - return util.ErrorResponse(fmt.Errorf("expected to find one room, but got %d", len(commonRooms))) + return util.ErrorResponse(fmt.Errorf("expected to find one room, but got %d", len(commonRooms))), nil } var ( @@ -153,19 +150,19 @@ func SendServerNotice( // create a new room for the user if len(commonRooms) == 0 { powerLevelContent := eventutil.InitialPowerLevelsContent(senderUserID) - powerLevelContent.Users[r.UserID] = -10 // taken from Synapse + powerLevelContent.Users[serverNoticeRequest.UserID] = -10 // taken from Synapse pl, err := json.Marshal(powerLevelContent) if err != nil { - return util.ErrorResponse(err) + return util.ErrorResponse(err), nil } createContent := map[string]interface{}{} createContent["m.federate"] = false cc, err := json.Marshal(createContent) if err != nil { - return util.ErrorResponse(err) + return util.ErrorResponse(err), nil } crReq := createRoomRequest{ - Invite: []string{r.UserID}, + Invite: []string{serverNoticeRequest.UserID}, Name: cfgNotices.RoomName, Visibility: "private", Preset: presetPrivateChat, @@ -187,36 +184,35 @@ func SendServerNotice( Order: 1.0, }, }} - if err = saveTagData(req, r.UserID, roomID, userAPI, serverAlertTag); err != nil { + if err = saveTagData(ctx, serverNoticeRequest.UserID, roomID, userAPI, serverAlertTag); err != nil { util.GetLogger(ctx).WithError(err).Error("saveTagData failed") - return jsonerror.InternalServerError() + return jsonerror.InternalServerError(), nil } default: // if we didn't get a createRoomResponse, we probably received an error, so return that. - return roomRes + return roomRes, nil } } else { - // we've found a room in common, check the membership roomID = commonRooms[0] // re-invite the user - res, err := sendInvite(ctx, accountsDB, senderDevice, roomID, r.UserID, "Server notice room", cfgClient, rsAPI, asAPI, time.Now()) + res, err := sendInvite(ctx, accountsDB, senderDevice, roomID, serverNoticeRequest.UserID, "Server notice room", cfgClient, rsAPI, asAPI, time.Now()) if err != nil { - return res + return res, nil } } startedGeneratingEvent := time.Now() request := map[string]interface{}{ - "body": r.Content.Body, - "msgtype": r.Content.MsgType, + "body": serverNoticeRequest.Content.Body, + "msgtype": serverNoticeRequest.Content.MsgType, } e, resErr := generateSendEvent(ctx, request, senderDevice, roomID, "m.room.message", nil, cfgClient, rsAPI, time.Now()) if resErr != nil { logrus.Errorf("failed to send message: %+v", resErr) - return *resErr + return *resErr, nil } timeToGenerateEvent := time.Since(startedGeneratingEvent) @@ -243,7 +239,7 @@ func SendServerNotice( false, ); err != nil { util.GetLogger(ctx).WithError(err).Error("SendEvents failed") - return jsonerror.InternalServerError() + return jsonerror.InternalServerError(), nil } util.GetLogger(ctx).WithFields(logrus.Fields{ "event_id": e.EventID(), @@ -266,7 +262,36 @@ func SendServerNotice( sendEventDuration.With(prometheus.Labels{"action": "build"}).Observe(float64(timeToGenerateEvent.Milliseconds())) sendEventDuration.With(prometheus.Labels{"action": "submit"}).Observe(float64(timeToSubmitEvent.Milliseconds())) - return res + return res, nil +} + +func getAllUserRooms(ctx context.Context, rsAPI api.RoomserverInternalAPI, userID string) ([]string, error) { + allUserRooms := []string{} + userRooms := api.QueryRoomsForUserResponse{} + if err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{ + UserID: userID, + WantMembership: "join", + }, &userRooms); err != nil { + return nil, err + } + allUserRooms = append(allUserRooms, userRooms.RoomIDs...) + // get invites for specified user + if err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{ + UserID: userID, + WantMembership: "invite", + }, &userRooms); err != nil { + return nil, err + } + allUserRooms = append(allUserRooms, userRooms.RoomIDs...) + // get left rooms for specified user + if err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{ + UserID: userID, + WantMembership: "leave", + }, &userRooms); err != nil { + return nil, err + } + allUserRooms = append(allUserRooms, userRooms.RoomIDs...) + return allUserRooms, nil } func (r sendServerNoticeRequest) valid() (ok bool) { From c0845ea1adc1784c6d70574adb073c876bf9704f Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 21 Feb 2022 14:44:16 +0100 Subject: [PATCH 25/56] Update logging --- clientapi/routing/consent_tracking.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/clientapi/routing/consent_tracking.go b/clientapi/routing/consent_tracking.go index ca33da08c8..1ed7fd16c3 100644 --- a/clientapi/routing/consent_tracking.go +++ b/clientapi/routing/consent_tracking.go @@ -117,7 +117,6 @@ func sendServerNoticeForConsent(userAPI userapi.UserInternalAPI, rsAPI api.Rooms accountsDB userdb.Database, asAPI appserviceAPI.AppServiceQueryAPI, ) { - logrus.Infof("Sending server notice to users who have not yet accepted the policy") res := &userapi.QueryOutdatedPolicyUsersResponse{} if err := userAPI.GetOutdatedPolicy(context.Background(), &userapi.QueryOutdatedPolicyUsersRequest{ PolicyVersion: cfgClient.Matrix.UserConsentOptions.Version, @@ -126,10 +125,17 @@ func sendServerNoticeForConsent(userAPI userapi.UserInternalAPI, rsAPI api.Rooms return } - consentOpts := cfgClient.Matrix.UserConsentOptions - data := make(map[string]string) - var err error - sentMessages := 0 + var ( + consentOpts = cfgClient.Matrix.UserConsentOptions + data = make(map[string]string) + err error + sentMessages int + ) + + if len(res.OutdatedUsers) > 0 { + logrus.WithField("count", len(res.OutdatedUsers)).Infof("Sending server notice to users who have not yet accepted the policy") + } + for _, userID := range res.OutdatedUsers { if userID == cfgClient.Matrix.ServerNotices.LocalPart { continue @@ -140,7 +146,6 @@ func sendServerNoticeForConsent(userAPI userapi.UserInternalAPI, rsAPI api.Rooms logrus.WithError(err).WithField("userID", userID).Error("unable to construct consentURI") continue } - logrus.Debugf("sending message to %s", userID) msgBody := &bytes.Buffer{} if err = consentOpts.TextTemplates.ExecuteTemplate(msgBody, "serverNoticeTemplate", data); err != nil { @@ -174,7 +179,9 @@ func sendServerNoticeForConsent(userAPI userapi.UserInternalAPI, rsAPI api.Rooms continue } } - logrus.Infof("Send messages to %d users", sentMessages) + if sentMessages > 0 { + logrus.Infof("Sent messages to %d users", sentMessages) + } } func buildConsentURI(cfgClient *config.ClientAPI, userID string) (string, error) { From 185cb7a582e19e20864e71128edba890635f46b4 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 21 Feb 2022 16:22:25 +0100 Subject: [PATCH 26/56] Remove BaseURL from Global Update template --- dendrite-config.yaml | 2 ++ docs/templates/privacy/1.0.gohtml | 6 +++--- setup/config/config.go | 2 +- setup/config/config_global.go | 14 +++++--------- setup/flags.go | 2 -- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/dendrite-config.yaml b/dendrite-config.yaml index e2e29a9822..66dc5e0497 100644 --- a/dendrite-config.yaml +++ b/dendrite-config.yaml @@ -84,6 +84,8 @@ global: user_consent: # If the user consent tracking is enabled or not enabled: false + # The base URL this homeserver will serve clients on, e.g. https://matrix.org + base_url: http://localhost # Randomly generated string to be used to calculate the HMAC form_secret: "superSecretRandomlyGeneratedSecret" # Require consent when user registers for the first time diff --git a/docs/templates/privacy/1.0.gohtml b/docs/templates/privacy/1.0.gohtml index 54438c4e05..8704ff2e2f 100644 --- a/docs/templates/privacy/1.0.gohtml +++ b/docs/templates/privacy/1.0.gohtml @@ -1,16 +1,16 @@ - Matrix.org Privacy policy + Privacy policy {{ if .HasConsented }}

- Your base already belong to us. + You have already given your consent.

{{ else }}

- All your base are belong to us. + Please give your consent to keep using this homeserver.

{{ if not .PublicVersion }} diff --git a/setup/config/config.go b/setup/config/config.go index 8a15240aa9..8559af23bd 100644 --- a/setup/config/config.go +++ b/setup/config/config.go @@ -289,7 +289,7 @@ func (config *Dendrite) Derive() error { // TODO: Add MSISDN auth type if config.Global.UserConsentOptions.Enabled && config.Global.UserConsentOptions.RequireAtRegistration { - uri := config.Global.BaseURL + "/_matrix/consent?v=" + config.Global.UserConsentOptions.Version + uri := config.Global.UserConsentOptions.BaseURL + "/_matrix/consent?v=" + config.Global.UserConsentOptions.Version config.Derived.Registration.Params[authtypes.LoginTypeTerms] = Terms{ Policies: Policies{ PrivacyPolicy: PrivacyPolicy{ diff --git a/setup/config/config_global.go b/setup/config/config_global.go index 22be162a49..19eec8be45 100644 --- a/setup/config/config_global.go +++ b/setup/config/config_global.go @@ -16,9 +16,6 @@ type Global struct { // The name of the server. This is usually the domain name, e.g 'matrix.org', 'localhost'. ServerName gomatrixserverlib.ServerName `yaml:"server_name"` - // The base URL this homeserver will server clients on, e.g. https://matrix.org - BaseURL string `yaml:"base_url"` - // Path to the private key which will be used to sign requests and events. PrivateKeyPath Path `yaml:"private_key"` @@ -75,7 +72,6 @@ type Global struct { func (c *Global) Defaults(generate bool) { if generate { c.ServerName = "localhost" - c.BaseURL = "http://localhost" c.PrivateKeyPath = "matrix_key.pem" _, c.PrivateKey, _ = ed25519.GenerateKey(rand.New(rand.NewSource(0))) c.KeyID = "ed25519:auto" @@ -86,7 +82,7 @@ func (c *Global) Defaults(generate bool) { c.Metrics.Defaults(generate) c.DNSCache.Defaults() c.Sentry.Defaults() - c.UserConsentOptions.Defaults(c.BaseURL) + c.UserConsentOptions.Defaults() c.ServerNotices.Defaults(generate) } @@ -268,18 +264,18 @@ type UserConsentOptions struct { // All loaded templates Templates *template.Template `yaml:"-"` TextTemplates *textTemplate.Template `yaml:"-"` - // The BaseURL, used for building the consent URL - BaseURL string `yaml:"-"` + // The base URL this homeserver will serve clients on, e.g. https://matrix.org + BaseURL string `yaml:"base_url"` } -func (c *UserConsentOptions) Defaults(baseURL string) { +func (c *UserConsentOptions) Defaults() { c.Enabled = false c.RequireAtRegistration = false c.SendServerNoticeToGuest = false c.PolicyName = "Privacy Policy" c.Version = "1.0" c.TemplateDir = "./templates/privacy" - c.BaseURL = baseURL + c.BaseURL = "http://localhost" } func (c *UserConsentOptions) Verify(configErrors *ConfigErrors, isMonolith bool) { diff --git a/setup/flags.go b/setup/flags.go index 2f06346197..09db51bc5e 100644 --- a/setup/flags.go +++ b/setup/flags.go @@ -43,8 +43,6 @@ func ParseFlags(monolith bool) *config.Dendrite { } cfg, err := config.Load(*configPath, monolith) - // TODO: just for testing - cfg.Global.UserConsentOptions.BaseURL = cfg.Global.BaseURL if err != nil { logrus.Fatalf("Invalid config file: %s", err) } From 61cdb714df204c2e77c882c9af842a4c2129f648 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 21 Feb 2022 16:23:28 +0100 Subject: [PATCH 27/56] Use typed values for Consent --- clientapi/clientapi.go | 1 + clientapi/routing/consent_tracking.go | 16 ++- clientapi/routing/routing.go | 158 +++++++++++++------------- internal/httputil/httpapi.go | 11 +- syncapi/routing/routing.go | 10 +- 5 files changed, 109 insertions(+), 87 deletions(-) diff --git a/clientapi/clientapi.go b/clientapi/clientapi.go index 48c2d531e4..45cc682eac 100644 --- a/clientapi/clientapi.go +++ b/clientapi/clientapi.go @@ -63,3 +63,4 @@ func AddPublicRoutes( syncProducer, transactionsCache, fsAPI, keyAPI, extRoomsProvider, mscCfg, ) } + diff --git a/clientapi/routing/consent_tracking.go b/clientapi/routing/consent_tracking.go index 1ed7fd16c3..38a011db69 100644 --- a/clientapi/routing/consent_tracking.go +++ b/clientapi/routing/consent_tracking.go @@ -1,3 +1,17 @@ +// Copyright 2022 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 ( @@ -194,7 +208,7 @@ func buildConsentURI(cfgClient *config.ClientAPI, userID string) (string, error) } userMAC := mac.Sum(nil) - return fmt.Sprintf("%s/_matrix/consent?u=%s&h=%s&v=%s", consentOpts.BaseURL, userID, userMAC, consentOpts.Version), nil + return fmt.Sprintf("%s/_matrix/consent?u=%s&h=%s&v=%s", cfgClient.Matrix.UserConsentOptions.BaseURL, userID, userMAC, consentOpts.Version), nil } func validHMAC(username, userHMAC, secret string) (bool, error) { diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index 0fa4885bf2..51b75cb607 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -131,7 +131,7 @@ func Setup( } synapseAdminRouter.Handle("/admin/v1/send_server_notice/{txnID}", - httputil.MakeAuthAPI("send_server_notice", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_server_notice", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { // not specced, but ensure we're rate limiting requests to this endpoint if r := rateLimits.Limit(req); r != nil { return *r @@ -151,7 +151,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) synapseAdminRouter.Handle("/admin/v1/send_server_notice", - httputil.MakeAuthAPI("send_server_notice", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_server_notice", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { // not specced, but ensure we're rate limiting requests to this endpoint if r := rateLimits.Limit(req); r != nil { return *r @@ -193,12 +193,12 @@ func Setup( unstableMux := publicAPIMux.PathPrefix("/unstable").Subrouter() v3mux.Handle("/createRoom", - httputil.MakeAuthAPI("createRoom", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("createRoom", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return CreateRoom(req, device, cfg, accountDB, rsAPI, asAPI) }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/join/{roomIDOrAlias}", - httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -214,7 +214,7 @@ func Setup( if mscCfg.Enabled("msc2753") { v3mux.Handle("/peek/{roomIDOrAlias}", - httputil.MakeAuthAPI(gomatrixserverlib.Peek, userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI(gomatrixserverlib.Peek, userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -229,12 +229,12 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) } v3mux.Handle("/joined_rooms", - httputil.MakeAuthAPI("joined_rooms", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("joined_rooms", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetJoinedRooms(req, device, rsAPI) }), ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/join", - httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -248,7 +248,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/leave", - httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -262,7 +262,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/unpeek", - httputil.MakeAuthAPI("unpeek", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("unpeek", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -273,7 +273,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/ban", - httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -282,7 +282,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/invite", - httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -294,7 +294,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/kick", - httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -303,7 +303,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/unban", - httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -312,7 +312,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/send/{eventType}", - httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -321,7 +321,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/send/{eventType}/{txnID}", - httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -332,7 +332,7 @@ func Setup( }), ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/event/{eventID}", - httputil.MakeAuthAPI("rooms_get_event", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_get_event", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -341,7 +341,7 @@ func Setup( }), ).Methods(http.MethodGet, http.MethodOptions) - v3mux.Handle("/rooms/{roomID}/state", httputil.MakeAuthAPI("room_state", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + v3mux.Handle("/rooms/{roomID}/state", httputil.MakeAuthAPI("room_state", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -349,7 +349,7 @@ func Setup( return OnIncomingStateRequest(req.Context(), device, rsAPI, vars["roomID"]) })).Methods(http.MethodGet, http.MethodOptions) - v3mux.Handle("/rooms/{roomID}/aliases", httputil.MakeAuthAPI("aliases", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + v3mux.Handle("/rooms/{roomID}/aliases", httputil.MakeAuthAPI("aliases", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -357,7 +357,7 @@ func Setup( return GetAliases(req, rsAPI, device, vars["roomID"]) })).Methods(http.MethodGet, http.MethodOptions) - v3mux.Handle("/rooms/{roomID}/state/{type:[^/]+/?}", httputil.MakeAuthAPI("room_state", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + v3mux.Handle("/rooms/{roomID}/state/{type:[^/]+/?}", httputil.MakeAuthAPI("room_state", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -368,7 +368,7 @@ func Setup( return OnIncomingStateTypeRequest(req.Context(), device, rsAPI, vars["roomID"], eventType, "", eventFormat) })).Methods(http.MethodGet, http.MethodOptions) - v3mux.Handle("/rooms/{roomID}/state/{type}/{stateKey}", httputil.MakeAuthAPI("room_state", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + v3mux.Handle("/rooms/{roomID}/state/{type}/{stateKey}", httputil.MakeAuthAPI("room_state", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -378,7 +378,7 @@ func Setup( })).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/state/{eventType:[^/]+/?}", - httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -390,7 +390,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/state/{eventType}/{stateKey}", - httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -425,7 +425,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/directory/room/{roomAlias}", - httputil.MakeAuthAPI("directory_room", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("directory_room", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -435,7 +435,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/directory/room/{roomAlias}", - httputil.MakeAuthAPI("directory_room", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("directory_room", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -454,7 +454,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) // TODO: Add AS support v3mux.Handle("/directory/list/room/{roomID}", - httputil.MakeAuthAPI("directory_list", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("directory_list", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -469,19 +469,19 @@ func Setup( ).Methods(http.MethodGet, http.MethodPost, http.MethodOptions) v3mux.Handle("/logout", - httputil.MakeAuthAPI("logout", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("logout", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return Logout(req, userAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/logout/all", - httputil.MakeAuthAPI("logout", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("logout", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return LogoutAll(req, userAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/typing/{userID}", - httputil.MakeAuthAPI("rooms_typing", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_typing", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -493,7 +493,7 @@ func Setup( }), ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/redact/{eventID}", - httputil.MakeAuthAPI("rooms_redact", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_redact", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -502,7 +502,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/redact/{eventID}/{txnId}", - httputil.MakeAuthAPI("rooms_redact", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_redact", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -512,7 +512,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/sendToDevice/{eventType}/{txnID}", - httputil.MakeAuthAPI("send_to_device", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_to_device", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -526,7 +526,7 @@ func Setup( // rather than r0. It's an exact duplicate of the above handler. // TODO: Remove this if/when sytest is fixed! unstableMux.Handle("/sendToDevice/{eventType}/{txnID}", - httputil.MakeAuthAPI("send_to_device", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_to_device", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -537,7 +537,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/account/whoami", - httputil.MakeAuthAPI("whoami", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("whoami", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -546,7 +546,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/account/password", - httputil.MakeAuthAPI("password", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("password", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -555,7 +555,7 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/account/deactivate", - httputil.MakeAuthAPI("deactivate", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("deactivate", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -623,7 +623,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/profile/{userID}/avatar_url", - httputil.MakeAuthAPI("profile_avatar_url", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("profile_avatar_url", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -648,7 +648,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/profile/{userID}/displayname", - httputil.MakeAuthAPI("profile_displayname", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("profile_displayname", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -663,19 +663,19 @@ func Setup( // PUT requests, so we need to allow this method v3mux.Handle("/account/3pid", - httputil.MakeAuthAPI("account_3pid", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("account_3pid", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetAssociated3PIDs(req, accountDB, device) }), ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/account/3pid", - httputil.MakeAuthAPI("account_3pid", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("account_3pid", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return CheckAndSave3PIDAssociation(req, accountDB, device, cfg) }), ).Methods(http.MethodPost, http.MethodOptions) unstableMux.Handle("/account/3pid/delete", - httputil.MakeAuthAPI("account_3pid", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("account_3pid", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return Forget3PID(req, accountDB) }), ).Methods(http.MethodPost, http.MethodOptions) @@ -701,7 +701,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/voip/turnServer", - httputil.MakeAuthAPI("turn_server", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("turn_server", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -730,7 +730,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/user/{userID}/account_data/{type}", - httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -740,7 +740,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/user/{userID}/rooms/{roomID}/account_data/{type}", - httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -750,7 +750,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/user/{userID}/account_data/{type}", - httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -760,7 +760,7 @@ func Setup( ).Methods(http.MethodGet) v3mux.Handle("/user/{userID}/rooms/{roomID}/account_data/{type}", - httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -770,7 +770,7 @@ func Setup( ).Methods(http.MethodGet) v3mux.Handle("/admin/whois/{userID}", - httputil.MakeAuthAPI("admin_whois", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("admin_whois", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -780,7 +780,7 @@ func Setup( ).Methods(http.MethodGet) v3mux.Handle("/user/{userID}/openid/request_token", - httputil.MakeAuthAPI("openid_request_token", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("openid_request_token", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -793,7 +793,7 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/user_directory/search", - httputil.MakeAuthAPI("userdirectory_search", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("userdirectory_search", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -818,7 +818,7 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/members", - httputil.MakeAuthAPI("rooms_members", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_members", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -828,7 +828,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/joined_members", - httputil.MakeAuthAPI("rooms_members", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_members", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -838,7 +838,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/read_markers", - httputil.MakeAuthAPI("rooms_read_markers", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_read_markers", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -851,7 +851,7 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/forget", - httputil.MakeAuthAPI("rooms_forget", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_forget", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -864,13 +864,13 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/devices", - httputil.MakeAuthAPI("get_devices", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_devices", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetDevicesByLocalpart(req, userAPI, device) }), ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/devices/{deviceID}", - httputil.MakeAuthAPI("get_device", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_device", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -880,7 +880,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/devices/{deviceID}", - httputil.MakeAuthAPI("device_data", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("device_data", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -890,7 +890,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/devices/{deviceID}", - httputil.MakeAuthAPI("delete_device", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("delete_device", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -900,7 +900,7 @@ func Setup( ).Methods(http.MethodDelete, http.MethodOptions) v3mux.Handle("/delete_devices", - httputil.MakeAuthAPI("delete_devices", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("delete_devices", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return DeleteDevices(req, userAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) @@ -925,7 +925,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/user/{userId}/rooms/{roomId}/tags", - httputil.MakeAuthAPI("get_tags", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_tags", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -935,7 +935,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/user/{userId}/rooms/{roomId}/tags/{tag}", - httputil.MakeAuthAPI("put_tag", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("put_tag", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -945,7 +945,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/user/{userId}/rooms/{roomId}/tags/{tag}", - httputil.MakeAuthAPI("delete_tag", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("delete_tag", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -955,7 +955,7 @@ func Setup( ).Methods(http.MethodDelete, http.MethodOptions) v3mux.Handle("/capabilities", - httputil.MakeAuthAPI("capabilities", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("capabilities", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -965,7 +965,7 @@ func Setup( // Key Backup Versions (Metadata) - getBackupKeysVersion := httputil.MakeAuthAPI("get_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getBackupKeysVersion := httputil.MakeAuthAPI("get_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -973,11 +973,11 @@ func Setup( return KeyBackupVersion(req, userAPI, device, vars["version"]) }) - getLatestBackupKeysVersion := httputil.MakeAuthAPI("get_latest_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getLatestBackupKeysVersion := httputil.MakeAuthAPI("get_latest_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return KeyBackupVersion(req, userAPI, device, "") }) - putBackupKeysVersion := httputil.MakeAuthAPI("put_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + putBackupKeysVersion := httputil.MakeAuthAPI("put_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -985,7 +985,7 @@ func Setup( return ModifyKeyBackupVersionAuthData(req, userAPI, device, vars["version"]) }) - deleteBackupKeysVersion := httputil.MakeAuthAPI("delete_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + deleteBackupKeysVersion := httputil.MakeAuthAPI("delete_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -993,7 +993,7 @@ func Setup( return DeleteKeyBackupVersion(req, userAPI, device, vars["version"]) }) - postNewBackupKeysVersion := httputil.MakeAuthAPI("post_new_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + postNewBackupKeysVersion := httputil.MakeAuthAPI("post_new_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return CreateKeyBackupVersion(req, userAPI, device) }) @@ -1012,7 +1012,7 @@ func Setup( // Inserting E2E Backup Keys // Bulk room and session - putBackupKeys := httputil.MakeAuthAPI("put_backup_keys", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + putBackupKeys := httputil.MakeAuthAPI("put_backup_keys", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { version := req.URL.Query().Get("version") if version == "" { return util.JSONResponse{ @@ -1029,7 +1029,7 @@ func Setup( }) // Single room bulk session - putBackupKeysRoom := httputil.MakeAuthAPI("put_backup_keys_room", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + putBackupKeysRoom := httputil.MakeAuthAPI("put_backup_keys_room", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -1061,7 +1061,7 @@ func Setup( }) // Single room, single session - putBackupKeysRoomSession := httputil.MakeAuthAPI("put_backup_keys_room_session", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + putBackupKeysRoomSession := httputil.MakeAuthAPI("put_backup_keys_room_session", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -1103,11 +1103,11 @@ func Setup( // Querying E2E Backup Keys - getBackupKeys := httputil.MakeAuthAPI("get_backup_keys", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getBackupKeys := httputil.MakeAuthAPI("get_backup_keys", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetBackupKeys(req, userAPI, device, req.URL.Query().Get("version"), "", "") }) - getBackupKeysRoom := httputil.MakeAuthAPI("get_backup_keys_room", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getBackupKeysRoom := httputil.MakeAuthAPI("get_backup_keys_room", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -1115,7 +1115,7 @@ func Setup( return GetBackupKeys(req, userAPI, device, req.URL.Query().Get("version"), vars["roomID"], "") }) - getBackupKeysRoomSession := httputil.MakeAuthAPI("get_backup_keys_room_session", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getBackupKeysRoomSession := httputil.MakeAuthAPI("get_backup_keys_room_session", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -1135,11 +1135,11 @@ func Setup( // Cross-signing device keys - postDeviceSigningKeys := httputil.MakeAuthAPI("post_device_signing_keys", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + postDeviceSigningKeys := httputil.MakeAuthAPI("post_device_signing_keys", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return UploadCrossSigningDeviceKeys(req, userInteractiveAuth, keyAPI, device, accountDB, cfg) }) - postDeviceSigningSignatures := httputil.MakeAuthAPI("post_device_signing_signatures", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + postDeviceSigningSignatures := httputil.MakeAuthAPI("post_device_signing_signatures", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return UploadCrossSigningDeviceSignatures(req, keyAPI, device) }) @@ -1151,27 +1151,27 @@ func Setup( // Supplying a device ID is deprecated. v3mux.Handle("/keys/upload/{deviceID}", - httputil.MakeAuthAPI("keys_upload", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("keys_upload", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return UploadKeys(req, keyAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/keys/upload", - httputil.MakeAuthAPI("keys_upload", userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("keys_upload", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return UploadKeys(req, keyAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/keys/query", - httputil.MakeAuthAPI("keys_query", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("keys_query", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return QueryKeys(req, keyAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/keys/claim", - httputil.MakeAuthAPI("keys_claim", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("keys_claim", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return ClaimKeys(req, keyAPI) }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomId}/receipt/{receiptType}/{eventId}", - httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, true, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } diff --git a/internal/httputil/httpapi.go b/internal/httputil/httpapi.go index d8c09d9cc2..d0fb71f7d4 100644 --- a/internal/httputil/httpapi.go +++ b/internal/httputil/httpapi.go @@ -53,12 +53,19 @@ type BasicAuth struct { Password string `yaml:"password"` } +type Consent bool + +const ( + ConsentRequired Consent = true + ConsentNotRequired Consent = false +) + // MakeAuthAPI turns a util.JSONRequestHandler function into an http.Handler which authenticates the request. func MakeAuthAPI( metricsName string, userAPI userapi.UserInternalAPI, userConsentCfg config.UserConsentOptions, - requireConsent bool, + requireConsent Consent, f func(*http.Request, *userapi.Device) util.JSONResponse, ) http.Handler { h := func(req *http.Request) util.JSONResponse { @@ -87,7 +94,7 @@ func MakeAuthAPI( } }() - if userConsentCfg.Enabled && requireConsent { + if userConsentCfg.Enabled && requireConsent == ConsentRequired { consentError := checkConsent(req.Context(), device.UserID, userAPI, userConsentCfg) if consentError != nil { return util.JSONResponse{ diff --git a/syncapi/routing/routing.go b/syncapi/routing/routing.go index d0d3ac4b06..ec1482791c 100644 --- a/syncapi/routing/routing.go +++ b/syncapi/routing/routing.go @@ -42,11 +42,11 @@ func Setup( v3mux := csMux.PathPrefix("/{apiversion:(?:r0|v3)}/").Subrouter() // TODO: Add AS support for all handlers below. - v3mux.Handle("/sync", httputil.MakeAuthAPI("sync", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + v3mux.Handle("/sync", httputil.MakeAuthAPI("sync", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return srp.OnIncomingSyncRequest(req, device) })).Methods(http.MethodGet, http.MethodOptions) - v3mux.Handle("/rooms/{roomID}/messages", httputil.MakeAuthAPI("room_messages", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + v3mux.Handle("/rooms/{roomID}/messages", httputil.MakeAuthAPI("room_messages", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -55,7 +55,7 @@ func Setup( })).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/user/{userId}/filter", - httputil.MakeAuthAPI("put_filter", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("put_filter", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -65,7 +65,7 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/user/{userId}/filter/{filterId}", - httputil.MakeAuthAPI("get_filter", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_filter", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -74,7 +74,7 @@ func Setup( }), ).Methods(http.MethodGet, http.MethodOptions) - v3mux.Handle("/keys/changes", httputil.MakeAuthAPI("keys_changes", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + v3mux.Handle("/keys/changes", httputil.MakeAuthAPI("keys_changes", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return srp.OnIncomingKeyChangeRequest(req, device) })).Methods(http.MethodGet, http.MethodOptions) } From c2b6019c357c16b4d20cb18676adc0cd1e7dea86 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 21 Feb 2022 16:40:59 +0100 Subject: [PATCH 28/56] Add possibility to query all membership states --- clientapi/routing/server_notices.go | 22 ++----------------- .../storage/postgres/membership_table.go | 20 +++++++++++++++-- roomserver/storage/shared/storage.go | 2 ++ .../storage/sqlite3/membership_table.go | 20 +++++++++++++++-- roomserver/storage/tables/interface.go | 1 + 5 files changed, 41 insertions(+), 24 deletions(-) diff --git a/clientapi/routing/server_notices.go b/clientapi/routing/server_notices.go index 40e42d72a3..59c2db4e08 100644 --- a/clientapi/routing/server_notices.go +++ b/clientapi/routing/server_notices.go @@ -266,32 +266,14 @@ func sendServerNotice( } func getAllUserRooms(ctx context.Context, rsAPI api.RoomserverInternalAPI, userID string) ([]string, error) { - allUserRooms := []string{} userRooms := api.QueryRoomsForUserResponse{} if err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{ UserID: userID, - WantMembership: "join", - }, &userRooms); err != nil { - return nil, err - } - allUserRooms = append(allUserRooms, userRooms.RoomIDs...) - // get invites for specified user - if err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{ - UserID: userID, - WantMembership: "invite", - }, &userRooms); err != nil { - return nil, err - } - allUserRooms = append(allUserRooms, userRooms.RoomIDs...) - // get left rooms for specified user - if err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{ - UserID: userID, - WantMembership: "leave", + WantMembership: "all", }, &userRooms); err != nil { return nil, err } - allUserRooms = append(allUserRooms, userRooms.RoomIDs...) - return allUserRooms, nil + return userRooms.RoomIDs, nil } func (r sendServerNoticeRequest) valid() (ok bool) { diff --git a/roomserver/storage/postgres/membership_table.go b/roomserver/storage/postgres/membership_table.go index 48c2c35cd1..0106d3e686 100644 --- a/roomserver/storage/postgres/membership_table.go +++ b/roomserver/storage/postgres/membership_table.go @@ -114,6 +114,9 @@ const updateMembershipForgetRoom = "" + const selectRoomsWithMembershipSQL = "" + "SELECT room_nid FROM roomserver_membership WHERE membership_nid = $1 AND target_nid = $2 and forgotten = false" +const selectRoomsForUserSQL = "" + + "SELECT room_nid FROM roomserver_membership WHERE target_nid = $1 and forgotten = false" + // selectKnownUsersSQL uses a sub-select statement here to find rooms that the user is // joined to. Since this information is used to populate the user directory, we will // only return users that the user would ordinarily be able to see anyway. @@ -157,6 +160,7 @@ type membershipStatements struct { updateMembershipForgetRoomStmt *sql.Stmt selectLocalServerInRoomStmt *sql.Stmt selectServerInRoomStmt *sql.Stmt + selectRoomsForUserStmt *sql.Stmt } func createMembershipTable(db *sql.DB) error { @@ -182,6 +186,7 @@ func prepareMembershipTable(db *sql.DB) (tables.Membership, error) { {&s.updateMembershipForgetRoomStmt, updateMembershipForgetRoom}, {&s.selectLocalServerInRoomStmt, selectLocalServerInRoomSQL}, {&s.selectServerInRoomStmt, selectServerInRoomSQL}, + {&s.selectRoomsForUserStmt, selectRoomsForUserSQL}, }.Prepare(db) } @@ -286,8 +291,19 @@ func (s *membershipStatements) SelectRoomsWithMembership( ctx context.Context, txn *sql.Tx, userID types.EventStateKeyNID, membershipState tables.MembershipState, ) ([]types.RoomNID, error) { - stmt := sqlutil.TxStmt(txn, s.selectRoomsWithMembershipStmt) - rows, err := stmt.QueryContext(ctx, membershipState, userID) + var ( + rows *sql.Rows + err error + ) + + if membershipState == tables.MemberShipStateAll { + stmt := sqlutil.TxStmt(txn, s.selectRoomsForUserStmt) + rows, err = stmt.QueryContext(ctx, userID) + } else { + stmt := sqlutil.TxStmt(txn, s.selectMembershipsFromRoomStmt) + rows, err = stmt.QueryContext(ctx, membershipState, userID) + } + if err != nil { return nil, err } diff --git a/roomserver/storage/shared/storage.go b/roomserver/storage/shared/storage.go index b255cfb3fe..45ebed0b00 100644 --- a/roomserver/storage/shared/storage.go +++ b/roomserver/storage/shared/storage.go @@ -967,6 +967,8 @@ func (d *Database) GetRoomsByMembership(ctx context.Context, userID, membership membershipState = tables.MembershipStateLeaveOrBan case "ban": membershipState = tables.MembershipStateLeaveOrBan + case "all": + membershipState = tables.MemberShipStateAll default: return nil, fmt.Errorf("GetRoomsByMembership: invalid membership %s", membership) } diff --git a/roomserver/storage/sqlite3/membership_table.go b/roomserver/storage/sqlite3/membership_table.go index 181b4b4c9a..e2b37da4d3 100644 --- a/roomserver/storage/sqlite3/membership_table.go +++ b/roomserver/storage/sqlite3/membership_table.go @@ -90,6 +90,9 @@ const updateMembershipForgetRoom = "" + const selectRoomsWithMembershipSQL = "" + "SELECT room_nid FROM roomserver_membership WHERE membership_nid = $1 AND target_nid = $2 and forgotten = false" +const selectRoomsForUserSQL = "" + + "SELECT room_nid FROM roomserver_membership WHERE target_nid = $1 and forgotten = false" + // selectKnownUsersSQL uses a sub-select statement here to find rooms that the user is // joined to. Since this information is used to populate the user directory, we will // only return users that the user would ordinarily be able to see anyway. @@ -133,6 +136,7 @@ type membershipStatements struct { updateMembershipForgetRoomStmt *sql.Stmt selectLocalServerInRoomStmt *sql.Stmt selectServerInRoomStmt *sql.Stmt + selectRoomsForUserStmt *sql.Stmt } func createMembershipTable(db *sql.DB) error { @@ -159,6 +163,7 @@ func prepareMembershipTable(db *sql.DB) (tables.Membership, error) { {&s.updateMembershipForgetRoomStmt, updateMembershipForgetRoom}, {&s.selectLocalServerInRoomStmt, selectLocalServerInRoomSQL}, {&s.selectServerInRoomStmt, selectServerInRoomSQL}, + {&s.selectRoomsForUserStmt, selectRoomsForUserSQL}, }.Prepare(db) } @@ -263,8 +268,19 @@ func (s *membershipStatements) UpdateMembership( func (s *membershipStatements) SelectRoomsWithMembership( ctx context.Context, txn *sql.Tx, userID types.EventStateKeyNID, membershipState tables.MembershipState, ) ([]types.RoomNID, error) { - stmt := sqlutil.TxStmt(txn, s.selectRoomsWithMembershipStmt) - rows, err := stmt.QueryContext(ctx, membershipState, userID) + var ( + rows *sql.Rows + err error + ) + + if membershipState == tables.MemberShipStateAll { + stmt := sqlutil.TxStmt(txn, s.selectRoomsForUserStmt) + rows, err = stmt.QueryContext(ctx, userID) + } else { + stmt := sqlutil.TxStmt(txn, s.selectMembershipsFromRoomStmt) + rows, err = stmt.QueryContext(ctx, membershipState, userID) + } + if err != nil { return nil, err } diff --git a/roomserver/storage/tables/interface.go b/roomserver/storage/tables/interface.go index e3fed700bb..468a427ff0 100644 --- a/roomserver/storage/tables/interface.go +++ b/roomserver/storage/tables/interface.go @@ -113,6 +113,7 @@ type Invites interface { type MembershipState int64 const ( + MemberShipStateAll MembershipState = 0 MembershipStateLeaveOrBan MembershipState = 1 MembershipStateInvite MembershipState = 2 MembershipStateJoin MembershipState = 3 From dac29c1786a1d0275aa91dcc631934ffa8cfb722 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 21 Feb 2022 16:43:28 +0100 Subject: [PATCH 29/56] Split migrations in to two statements --- .../deltas/2022021414375800_add_policy_version.go | 8 ++++++-- .../sqlite3/deltas/2022021414375800_add_policy_version.go | 7 +++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/userapi/storage/postgres/deltas/2022021414375800_add_policy_version.go b/userapi/storage/postgres/deltas/2022021414375800_add_policy_version.go index 10b1006df7..27347d21c3 100644 --- a/userapi/storage/postgres/deltas/2022021414375800_add_policy_version.go +++ b/userapi/storage/postgres/deltas/2022021414375800_add_policy_version.go @@ -12,11 +12,15 @@ func LoadAddPolicyVersion(m *sqlutil.Migrations) { } func UpAddPolicyVersion(tx *sql.Tx) error { - _, err := tx.Exec("ALTER TABLE account_accounts ADD COLUMN IF NOT EXISTS policy_version TEXT;" + - "ALTER TABLE account_accounts ADD COLUMN IF NOT EXISTS policy_version_sent TEXT;") + _, err := tx.Exec("ALTER TABLE account_accounts ADD COLUMN IF NOT EXISTS policy_version TEXT;") if err != nil { return fmt.Errorf("failed to execute upgrade: %w", err) } + _, err = tx.Exec("ALTER TABLE account_accounts ADD COLUMN IF NOT EXISTS policy_version_sent TEXT;") + if err != nil { + return fmt.Errorf("failed to execute upgrade: %w", err) + } + return nil } diff --git a/userapi/storage/sqlite3/deltas/2022021414375800_add_policy_version.go b/userapi/storage/sqlite3/deltas/2022021414375800_add_policy_version.go index 2292b90319..683210ca74 100644 --- a/userapi/storage/sqlite3/deltas/2022021414375800_add_policy_version.go +++ b/userapi/storage/sqlite3/deltas/2022021414375800_add_policy_version.go @@ -12,8 +12,11 @@ func LoadAddPolicyVersion(m *sqlutil.Migrations) { } func UpAddPolicyVersion(tx *sql.Tx) error { - _, err := tx.Exec("ALTER TABLE account_accounts ADD COLUMN policy_version TEXT;" + - "ALTER TABLE account_accounts ADD COLUMN policy_version_sent TEXT;") + _, err := tx.Exec("ALTER TABLE account_accounts ADD COLUMN policy_version TEXT;") + if err != nil { + return fmt.Errorf("failed to execute upgrade: %w", err) + } + _, err = tx.Exec("ALTER TABLE account_accounts ADD COLUMN policy_version_sent TEXT;") if err != nil { return fmt.Errorf("failed to execute upgrade: %w", err) } From c65eb2bf5224085f0f607ad9720a5c7c78bf6908 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 21 Feb 2022 16:57:24 +0100 Subject: [PATCH 30/56] Fix query --- roomserver/storage/postgres/membership_table.go | 2 +- roomserver/storage/sqlite3/membership_table.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/roomserver/storage/postgres/membership_table.go b/roomserver/storage/postgres/membership_table.go index 0106d3e686..e98a87b045 100644 --- a/roomserver/storage/postgres/membership_table.go +++ b/roomserver/storage/postgres/membership_table.go @@ -300,7 +300,7 @@ func (s *membershipStatements) SelectRoomsWithMembership( stmt := sqlutil.TxStmt(txn, s.selectRoomsForUserStmt) rows, err = stmt.QueryContext(ctx, userID) } else { - stmt := sqlutil.TxStmt(txn, s.selectMembershipsFromRoomStmt) + stmt := sqlutil.TxStmt(txn, s.selectRoomsWithMembershipStmt) rows, err = stmt.QueryContext(ctx, membershipState, userID) } diff --git a/roomserver/storage/sqlite3/membership_table.go b/roomserver/storage/sqlite3/membership_table.go index e2b37da4d3..f852422e7a 100644 --- a/roomserver/storage/sqlite3/membership_table.go +++ b/roomserver/storage/sqlite3/membership_table.go @@ -277,7 +277,7 @@ func (s *membershipStatements) SelectRoomsWithMembership( stmt := sqlutil.TxStmt(txn, s.selectRoomsForUserStmt) rows, err = stmt.QueryContext(ctx, userID) } else { - stmt := sqlutil.TxStmt(txn, s.selectMembershipsFromRoomStmt) + stmt := sqlutil.TxStmt(txn, s.selectRoomsWithMembershipStmt) rows, err = stmt.QueryContext(ctx, membershipState, userID) } From 4f2d1614018a330dcc84c06f3cf78b044fb00f9f Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 21 Feb 2022 17:09:25 +0100 Subject: [PATCH 31/56] Remove consentMux --- build/gobind-pinecone/monolith.go | 1 - build/gobind-yggdrasil/monolith.go | 1 - clientapi/clientapi.go | 3 +-- clientapi/routing/consent_tracking.go | 2 +- clientapi/routing/routing.go | 6 +++--- cmd/dendrite-demo-libp2p/main.go | 1 - cmd/dendrite-demo-pinecone/main.go | 1 - cmd/dendrite-demo-yggdrasil/main.go | 1 - cmd/dendrite-monolith-server/main.go | 1 - cmd/dendrite-polylith-multi/personalities/clientapi.go | 2 +- cmd/dendritejs-pinecone/main.go | 1 - cmd/dendritejs/main.go | 1 - internal/httputil/httpapi.go | 2 +- setup/base/base.go | 3 --- setup/config/config.go | 2 +- setup/monolith.go | 4 ++-- 16 files changed, 10 insertions(+), 22 deletions(-) diff --git a/build/gobind-pinecone/monolith.go b/build/gobind-pinecone/monolith.go index ed11ee749b..aa8cc6e6eb 100644 --- a/build/gobind-pinecone/monolith.go +++ b/build/gobind-pinecone/monolith.go @@ -348,7 +348,6 @@ func (m *DendriteMonolith) Start() { base.PublicWellKnownAPIMux, base.PublicMediaAPIMux, base.SynapseAdminMux, - base.PublicConsentAPIMux, ) httpRouter := mux.NewRouter().SkipClean(true).UseEncodedPath() diff --git a/build/gobind-yggdrasil/monolith.go b/build/gobind-yggdrasil/monolith.go index cac4512cb2..8b9c88f2a0 100644 --- a/build/gobind-yggdrasil/monolith.go +++ b/build/gobind-yggdrasil/monolith.go @@ -155,7 +155,6 @@ func (m *DendriteMonolith) Start() { base.PublicWellKnownAPIMux, base.PublicMediaAPIMux, base.SynapseAdminMux, - base.PublicConsentAPIMux, ) httpRouter := mux.NewRouter() diff --git a/clientapi/clientapi.go b/clientapi/clientapi.go index 45cc682eac..b1eb391f8d 100644 --- a/clientapi/clientapi.go +++ b/clientapi/clientapi.go @@ -36,7 +36,6 @@ import ( func AddPublicRoutes( router *mux.Router, synapseAdminRouter *mux.Router, - consentAPIMux *mux.Router, cfg *config.ClientAPI, accountsDB userdb.Database, federation *gomatrixserverlib.FederationClient, @@ -58,7 +57,7 @@ func AddPublicRoutes( } routing.Setup( - router, synapseAdminRouter, consentAPIMux, cfg, eduInputAPI, rsAPI, asAPI, + router, synapseAdminRouter, cfg, eduInputAPI, rsAPI, asAPI, accountsDB, userAPI, federation, syncProducer, transactionsCache, fsAPI, keyAPI, extRoomsProvider, mscCfg, ) diff --git a/clientapi/routing/consent_tracking.go b/clientapi/routing/consent_tracking.go index 38a011db69..a52f62b394 100644 --- a/clientapi/routing/consent_tracking.go +++ b/clientapi/routing/consent_tracking.go @@ -208,7 +208,7 @@ func buildConsentURI(cfgClient *config.ClientAPI, userID string) (string, error) } userMAC := mac.Sum(nil) - return fmt.Sprintf("%s/_matrix/consent?u=%s&h=%s&v=%s", cfgClient.Matrix.UserConsentOptions.BaseURL, userID, userMAC, consentOpts.Version), nil + return fmt.Sprintf("%s/_matrix/client/consent?u=%s&h=%s&v=%s", cfgClient.Matrix.UserConsentOptions.BaseURL, userID, userMAC, consentOpts.Version), nil } func validHMAC(username, userHMAC, secret string) (bool, error) { diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index 51b75cb607..06243cd4b9 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -48,7 +48,7 @@ import ( // applied: // nolint: gocyclo func Setup( - publicAPIMux, synapseAdminRouter, consentAPIMux *mux.Router, cfg *config.ClientAPI, + publicAPIMux, synapseAdminRouter *mux.Router, cfg *config.ClientAPI, eduAPI eduServerAPI.EDUServerInputAPI, rsAPI roomserverAPI.RoomserverInternalAPI, asAPI appserviceAPI.AppServiceQueryAPI, @@ -174,7 +174,7 @@ func Setup( // Note that 'apiversion' is chosen because it must not collide with a variable used in any of the routing! v3mux := publicAPIMux.PathPrefix("/{apiversion:(?:r0|v3)}/").Subrouter() - // unspecced consent tracking + // NOTSPEC: consent tracking if cfg.Matrix.UserConsentOptions.Enabled { if !cfg.Matrix.ServerNotices.Enabled { logrus.Warnf("Consent tracking is enabled, but server notes are not. No server notice will be sent to users") @@ -182,7 +182,7 @@ func Setup( // start a new go routine to send messages about consent go sendServerNoticeForConsent(userAPI, rsAPI, &cfg.Matrix.ServerNotices, cfg, serverNotificationSender, accountDB, asAPI) } - consentAPIMux.Handle("/consent", + publicAPIMux.Handle("/consent", httputil.MakeHTMLAPI("consent", func(writer http.ResponseWriter, request *http.Request) *util.JSONResponse { return consent(writer, request, userAPI, cfg) }), diff --git a/cmd/dendrite-demo-libp2p/main.go b/cmd/dendrite-demo-libp2p/main.go index d09f1cb559..78536901c5 100644 --- a/cmd/dendrite-demo-libp2p/main.go +++ b/cmd/dendrite-demo-libp2p/main.go @@ -193,7 +193,6 @@ func main() { base.Base.PublicWellKnownAPIMux, base.Base.PublicMediaAPIMux, base.Base.SynapseAdminMux, - base.Base.PublicConsentAPIMux, ) if err := mscs.Enable(&base.Base, &monolith); err != nil { logrus.WithError(err).Fatalf("Failed to enable MSCs") diff --git a/cmd/dendrite-demo-pinecone/main.go b/cmd/dendrite-demo-pinecone/main.go index 18c0474459..5810a7f186 100644 --- a/cmd/dendrite-demo-pinecone/main.go +++ b/cmd/dendrite-demo-pinecone/main.go @@ -221,7 +221,6 @@ func main() { base.PublicWellKnownAPIMux, base.PublicMediaAPIMux, base.SynapseAdminMux, - base.PublicConsentAPIMux, ) wsUpgrader := websocket.Upgrader{ diff --git a/cmd/dendrite-demo-yggdrasil/main.go b/cmd/dendrite-demo-yggdrasil/main.go index 544105c444..d16f0e9e58 100644 --- a/cmd/dendrite-demo-yggdrasil/main.go +++ b/cmd/dendrite-demo-yggdrasil/main.go @@ -156,7 +156,6 @@ func main() { base.PublicWellKnownAPIMux, base.PublicMediaAPIMux, base.SynapseAdminMux, - base.PublicConsentAPIMux, ) if err := mscs.Enable(base, &monolith); err != nil { logrus.WithError(err).Fatalf("Failed to enable MSCs") diff --git a/cmd/dendrite-monolith-server/main.go b/cmd/dendrite-monolith-server/main.go index 7d3e15af09..bb26852089 100644 --- a/cmd/dendrite-monolith-server/main.go +++ b/cmd/dendrite-monolith-server/main.go @@ -165,7 +165,6 @@ func main() { base.PublicWellKnownAPIMux, base.PublicMediaAPIMux, base.SynapseAdminMux, - base.PublicConsentAPIMux, ) if len(base.Cfg.MSCs.MSCs) > 0 { diff --git a/cmd/dendrite-polylith-multi/personalities/clientapi.go b/cmd/dendrite-polylith-multi/personalities/clientapi.go index 6c81bd8898..bd9f7a109b 100644 --- a/cmd/dendrite-polylith-multi/personalities/clientapi.go +++ b/cmd/dendrite-polylith-multi/personalities/clientapi.go @@ -33,7 +33,7 @@ func ClientAPI(base *basepkg.BaseDendrite, cfg *config.Dendrite) { keyAPI := base.KeyServerHTTPClient() clientapi.AddPublicRoutes( - base.PublicClientAPIMux, base.SynapseAdminMux, base.PublicConsentAPIMux, &base.Cfg.ClientAPI, accountDB, federation, + base.PublicClientAPIMux, base.SynapseAdminMux, &base.Cfg.ClientAPI, accountDB, federation, rsAPI, eduInputAPI, asQuery, transactions.New(), fsAPI, userAPI, keyAPI, nil, &cfg.MSCs, ) diff --git a/cmd/dendritejs-pinecone/main.go b/cmd/dendritejs-pinecone/main.go index 3c706a33db..664f644f3c 100644 --- a/cmd/dendritejs-pinecone/main.go +++ b/cmd/dendritejs-pinecone/main.go @@ -223,7 +223,6 @@ func startup() { base.PublicWellKnownAPIMux, base.PublicMediaAPIMux, base.SynapseAdminMux, - base.PublicConsentAPIMux, ) httpRouter := mux.NewRouter().SkipClean(true).UseEncodedPath() diff --git a/cmd/dendritejs/main.go b/cmd/dendritejs/main.go index 42ce97b15f..0ea41b4c4f 100644 --- a/cmd/dendritejs/main.go +++ b/cmd/dendritejs/main.go @@ -235,7 +235,6 @@ func main() { base.PublicKeyAPIMux, base.PublicMediaAPIMux, base.SynapseAdminMux, - base.PublicConsentAPIMux, ) httpRouter := mux.NewRouter().SkipClean(true).UseEncodedPath() diff --git a/internal/httputil/httpapi.go b/internal/httputil/httpapi.go index d0fb71f7d4..091c36c3b1 100644 --- a/internal/httputil/httpapi.go +++ b/internal/httputil/httpapi.go @@ -160,7 +160,7 @@ func getConsentURL(username string, config config.UserConsentOptions) (string, e hmac := hex.EncodeToString(mac.Sum(nil)) return fmt.Sprintf( - "%s/_matrix/consent?u=%s&h=%s&v=%s", + "%s/_matrix/client/consent?u=%s&h=%s&v=%s", config.BaseURL, username, hmac, config.Version, ), nil } diff --git a/setup/base/base.go b/setup/base/base.go index 5b6d1c14b3..4ab8769b6c 100644 --- a/setup/base/base.go +++ b/setup/base/base.go @@ -73,7 +73,6 @@ type BaseDendrite struct { PublicKeyAPIMux *mux.Router PublicMediaAPIMux *mux.Router PublicWellKnownAPIMux *mux.Router - PublicConsentAPIMux *mux.Router InternalAPIMux *mux.Router SynapseAdminMux *mux.Router UseHTTPAPIs bool @@ -205,7 +204,6 @@ func NewBaseDendrite(cfg *config.Dendrite, componentName string, options ...Base PublicKeyAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.PublicKeyPathPrefix).Subrouter().UseEncodedPath(), PublicMediaAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.PublicMediaPathPrefix).Subrouter().UseEncodedPath(), PublicWellKnownAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.PublicWellKnownPrefix).Subrouter().UseEncodedPath(), - PublicConsentAPIMux: mux.NewRouter().SkipClean(true).PathPrefix("/_matrix").Subrouter().UseEncodedPath(), InternalAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.InternalPathPrefix).Subrouter().UseEncodedPath(), SynapseAdminMux: mux.NewRouter().SkipClean(true).PathPrefix("/_synapse/").Subrouter().UseEncodedPath(), apiHttpClient: &apiClient, @@ -395,7 +393,6 @@ func (b *BaseDendrite) SetupAndServeHTTP( externalRouter.PathPrefix("/_synapse/").Handler(b.SynapseAdminMux) externalRouter.PathPrefix(httputil.PublicMediaPathPrefix).Handler(b.PublicMediaAPIMux) externalRouter.PathPrefix(httputil.PublicWellKnownPrefix).Handler(b.PublicWellKnownAPIMux) - externalRouter.PathPrefix("/_matrix").Handler(b.PublicConsentAPIMux) if internalAddr != NoListener && internalAddr != externalAddr { go func() { diff --git a/setup/config/config.go b/setup/config/config.go index 8559af23bd..33c5fbd4cd 100644 --- a/setup/config/config.go +++ b/setup/config/config.go @@ -289,7 +289,7 @@ func (config *Dendrite) Derive() error { // TODO: Add MSISDN auth type if config.Global.UserConsentOptions.Enabled && config.Global.UserConsentOptions.RequireAtRegistration { - uri := config.Global.UserConsentOptions.BaseURL + "/_matrix/consent?v=" + config.Global.UserConsentOptions.Version + uri := config.Global.UserConsentOptions.BaseURL + "/_matrix/client/consent?v=" + config.Global.UserConsentOptions.Version config.Derived.Registration.Params[authtypes.LoginTypeTerms] = Terms{ Policies: Policies{ PrivacyPolicy: PrivacyPolicy{ diff --git a/setup/monolith.go b/setup/monolith.go index d0325ee1e4..61125e4a91 100644 --- a/setup/monolith.go +++ b/setup/monolith.go @@ -55,9 +55,9 @@ type Monolith struct { } // AddAllPublicRoutes attaches all public paths to the given router -func (m *Monolith) AddAllPublicRoutes(process *process.ProcessContext, csMux, ssMux, keyMux, wkMux, mediaMux, synapseMux, consentMux *mux.Router) { +func (m *Monolith) AddAllPublicRoutes(process *process.ProcessContext, csMux, ssMux, keyMux, wkMux, mediaMux, synapseMux *mux.Router) { clientapi.AddPublicRoutes( - csMux, synapseMux, consentMux, &m.Config.ClientAPI, m.AccountDB, + csMux, synapseMux, &m.Config.ClientAPI, m.AccountDB, m.FedClient, m.RoomserverAPI, m.EDUInternalAPI, m.AppserviceAPI, transactions.New(), m.FederationAPI, m.UserAPI, m.KeyAPI, m.ExtPublicRoomsProvider, From ce658ab8f22e45fb5e9be586973d9aa9984e9cc7 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Thu, 24 Feb 2022 08:54:54 +0100 Subject: [PATCH 32/56] Fix missing params --- syncapi/routing/routing.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncapi/routing/routing.go b/syncapi/routing/routing.go index 51e68ca46e..700a56201b 100644 --- a/syncapi/routing/routing.go +++ b/syncapi/routing/routing.go @@ -79,7 +79,7 @@ func Setup( })).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/rooms/{roomId}/context/{eventId}", - httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) From 2ad15f308fd5f0833a3cddf312ae977415a98ade Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Fri, 25 Feb 2022 15:32:19 +0100 Subject: [PATCH 33/56] Rename some functions --- clientapi/routing/consent_tracking.go | 4 ++-- clientapi/routing/register_test.go | 4 ++-- userapi/api/api.go | 10 +++++----- userapi/api/api_trace.go | 6 +++--- userapi/internal/api.go | 6 +++--- userapi/inthttp/client.go | 8 ++++---- userapi/inthttp/server.go | 6 +++--- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/clientapi/routing/consent_tracking.go b/clientapi/routing/consent_tracking.go index a52f62b394..ec5b10a7eb 100644 --- a/clientapi/routing/consent_tracking.go +++ b/clientapi/routing/consent_tracking.go @@ -131,8 +131,8 @@ func sendServerNoticeForConsent(userAPI userapi.UserInternalAPI, rsAPI api.Rooms accountsDB userdb.Database, asAPI appserviceAPI.AppServiceQueryAPI, ) { - res := &userapi.QueryOutdatedPolicyUsersResponse{} - if err := userAPI.GetOutdatedPolicy(context.Background(), &userapi.QueryOutdatedPolicyUsersRequest{ + res := &userapi.QueryOutdatedPolicyResponse{} + if err := userAPI.QueryOutdatedPolicy(context.Background(), &userapi.QueryOutdatedPolicyRequest{ PolicyVersion: cfgClient.Matrix.UserConsentOptions.Version, }, res); err != nil { logrus.WithError(err).Error("unable to fetch users with outdated consent policy") diff --git a/clientapi/routing/register_test.go b/clientapi/routing/register_test.go index c6b7e61cf0..5df58fc351 100644 --- a/clientapi/routing/register_test.go +++ b/clientapi/routing/register_test.go @@ -219,7 +219,7 @@ func TestSessionCleanUp(t *testing.T) { // manually added, as s.addParams() would start the timer with the default timeout s.params[dummySession] = registerRequest{Username: "Testing"} s.startTimer(time.Millisecond, dummySession) - time.Sleep(time.Millisecond * 2) + time.Sleep(time.Millisecond * 5) if data, ok := s.getParams(dummySession); ok { t.Errorf("expected session to be deleted: %+v", data) } @@ -245,7 +245,7 @@ func TestSessionCleanUp(t *testing.T) { s.getCompletedStages(dummySession) // reset the timer with a lower timeout s.startTimer(time.Millisecond, dummySession) - time.Sleep(time.Millisecond * 2) + time.Sleep(time.Millisecond * 5) if data, ok := s.getParams(dummySession); ok { t.Errorf("expected session to be deleted: %+v", data) } diff --git a/userapi/api/api.go b/userapi/api/api.go index a402753071..6d89270434 100644 --- a/userapi/api/api.go +++ b/userapi/api/api.go @@ -46,7 +46,7 @@ type UserInternalAPI interface { QuerySearchProfiles(ctx context.Context, req *QuerySearchProfilesRequest, res *QuerySearchProfilesResponse) error QueryOpenIDToken(ctx context.Context, req *QueryOpenIDTokenRequest, res *QueryOpenIDTokenResponse) error QueryPolicyVersion(ctx context.Context, req *QueryPolicyVersionRequest, res *QueryPolicyVersionResponse) error - GetOutdatedPolicy(ctx context.Context, req *QueryOutdatedPolicyUsersRequest, res *QueryOutdatedPolicyUsersResponse) error + QueryOutdatedPolicy(ctx context.Context, req *QueryOutdatedPolicyRequest, res *QueryOutdatedPolicyResponse) error PerformUpdatePolicyVersion(ctx context.Context, req *UpdatePolicyVersionRequest, res *UpdatePolicyVersionResponse) error } @@ -349,13 +349,13 @@ type QueryPolicyVersionResponse struct { PolicyVersion string } -// QueryOutdatedPolicyUsersRequest is the request for QueryOutdatedPolicyUsersRequest -type QueryOutdatedPolicyUsersRequest struct { +// QueryOutdatedPolicyRequest is the request for QueryOutdatedPolicyRequest +type QueryOutdatedPolicyRequest struct { PolicyVersion string } -// QueryOutdatedPolicyUsersResponse is the response for QueryOutdatedPolicyUsersRequest -type QueryOutdatedPolicyUsersResponse struct { +// QueryOutdatedPolicyResponse is the response for QueryOutdatedPolicyRequest +type QueryOutdatedPolicyResponse struct { OutdatedUsers []string } diff --git a/userapi/api/api_trace.go b/userapi/api/api_trace.go index 3428e0fd36..9d142d34b2 100644 --- a/userapi/api/api_trace.go +++ b/userapi/api/api_trace.go @@ -125,9 +125,9 @@ func (t *UserInternalAPITrace) QueryPolicyVersion(ctx context.Context, req *Quer return err } -func (t *UserInternalAPITrace) GetOutdatedPolicy(ctx context.Context, req *QueryOutdatedPolicyUsersRequest, res *QueryOutdatedPolicyUsersResponse) error { - err := t.Impl.GetOutdatedPolicy(ctx, req, res) - util.GetLogger(ctx).Infof("GetOutdatedPolicy req=%+v res=%+v", js(req), js(res)) +func (t *UserInternalAPITrace) QueryOutdatedPolicy(ctx context.Context, req *QueryOutdatedPolicyRequest, res *QueryOutdatedPolicyResponse) error { + err := t.Impl.QueryOutdatedPolicy(ctx, req, res) + util.GetLogger(ctx).Infof("QueryOutdatedPolicy req=%+v res=%+v", js(req), js(res)) return err } diff --git a/userapi/internal/api.go b/userapi/internal/api.go index 862dd0c2c4..8ef6a09002 100644 --- a/userapi/internal/api.go +++ b/userapi/internal/api.go @@ -610,10 +610,10 @@ func (a *UserInternalAPI) QueryPolicyVersion( return nil } -func (a *UserInternalAPI) GetOutdatedPolicy( +func (a *UserInternalAPI) QueryOutdatedPolicy( ctx context.Context, - req *api.QueryOutdatedPolicyUsersRequest, - res *api.QueryOutdatedPolicyUsersResponse, + req *api.QueryOutdatedPolicyRequest, + res *api.QueryOutdatedPolicyResponse, ) error { var err error res.OutdatedUsers, err = a.DB.GetOutdatedPolicy(ctx, req.PolicyVersion) diff --git a/userapi/inthttp/client.go b/userapi/inthttp/client.go index 9067d993ed..b5176aa3e0 100644 --- a/userapi/inthttp/client.go +++ b/userapi/inthttp/client.go @@ -254,15 +254,15 @@ func (h *httpUserInternalAPI) QueryKeyBackup(ctx context.Context, req *api.Query } func (h *httpUserInternalAPI) QueryPolicyVersion(ctx context.Context, req *api.QueryPolicyVersionRequest, res *api.QueryPolicyVersionResponse) error { - span, ctx := opentracing.StartSpanFromContext(ctx, "QueryKeyBackup") + span, ctx := opentracing.StartSpanFromContext(ctx, "QueryPolicyVersion") defer span.Finish() apiURL := h.apiURL + QueryPolicyVersionPath return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) } -func (h *httpUserInternalAPI) GetOutdatedPolicy(ctx context.Context, req *api.QueryOutdatedPolicyUsersRequest, res *api.QueryOutdatedPolicyUsersResponse) error { - span, ctx := opentracing.StartSpanFromContext(ctx, "QueryKeyBackup") +func (h *httpUserInternalAPI) QueryOutdatedPolicy(ctx context.Context, req *api.QueryOutdatedPolicyRequest, res *api.QueryOutdatedPolicyResponse) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "QueryOutdatedPolicy") defer span.Finish() apiURL := h.apiURL + QueryOutdatedPolicyUsersPath @@ -270,7 +270,7 @@ func (h *httpUserInternalAPI) GetOutdatedPolicy(ctx context.Context, req *api.Qu } func (h *httpUserInternalAPI) PerformUpdatePolicyVersion(ctx context.Context, req *api.UpdatePolicyVersionRequest, res *api.UpdatePolicyVersionResponse) error { - span, ctx := opentracing.StartSpanFromContext(ctx, "QueryKeyBackup") + span, ctx := opentracing.StartSpanFromContext(ctx, "PerformUpdatePolicyVersion") defer span.Finish() apiURL := h.apiURL + PerformUpdatePolicyVersionPath diff --git a/userapi/inthttp/server.go b/userapi/inthttp/server.go index 70e8d17ceb..8dc0dee555 100644 --- a/userapi/inthttp/server.go +++ b/userapi/inthttp/server.go @@ -281,12 +281,12 @@ func AddRoutes(internalAPIMux *mux.Router, s api.UserInternalAPI) { ) internalAPIMux.Handle(QueryOutdatedPolicyUsersPath, httputil.MakeInternalAPI("queryOutdatedPolicyUsers", func(req *http.Request) util.JSONResponse { - request := api.QueryOutdatedPolicyUsersRequest{} - response := api.QueryOutdatedPolicyUsersResponse{} + request := api.QueryOutdatedPolicyRequest{} + response := api.QueryOutdatedPolicyResponse{} if err := json.NewDecoder(req.Body).Decode(&request); err != nil { return util.MessageResponse(http.StatusBadRequest, err.Error()) } - err := s.GetOutdatedPolicy(req.Context(), &request, &response) + err := s.QueryOutdatedPolicy(req.Context(), &request, &response) if err != nil { return util.JSONResponse{Code: http.StatusBadRequest, JSON: &response} } From df7218e2302510b122782060bbcd5fbe1924d31a Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Fri, 4 Mar 2022 09:30:46 +0100 Subject: [PATCH 34/56] Fix parameters --- clientapi/routing/routing.go | 34 +++++++++++++++++----------------- setup/mscs/msc2946/msc2946.go | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index 444024b021..06d8807da9 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -583,7 +583,7 @@ func Setup( // Push rules v3mux.Handle("/pushrules", - httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: jsonerror.InvalidArgumentValue("missing trailing slash"), @@ -592,13 +592,13 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushrules/", - httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetAllPushRules(req.Context(), device, userAPI) }), ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushrules/", - httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: jsonerror.InvalidArgumentValue("scope, kind and rule ID must be specified"), @@ -607,7 +607,7 @@ func Setup( ).Methods(http.MethodPut) v3mux.Handle("/pushrules/{scope}/", - httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -617,7 +617,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushrules/{scope}", - httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: jsonerror.InvalidArgumentValue("missing trailing slash after scope"), @@ -626,7 +626,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushrules/{scope:[^/]+/?}", - httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: jsonerror.InvalidArgumentValue("kind and rule ID must be specified"), @@ -635,7 +635,7 @@ func Setup( ).Methods(http.MethodPut) v3mux.Handle("/pushrules/{scope}/{kind}/", - httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -645,7 +645,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushrules/{scope}/{kind}", - httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: jsonerror.InvalidArgumentValue("missing trailing slash after kind"), @@ -654,7 +654,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushrules/{scope}/{kind:[^/]+/?}", - httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: jsonerror.InvalidArgumentValue("rule ID must be specified"), @@ -663,7 +663,7 @@ func Setup( ).Methods(http.MethodPut) v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}", - httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -673,7 +673,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}", - httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -687,7 +687,7 @@ func Setup( ).Methods(http.MethodPut) v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}", - httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -697,7 +697,7 @@ func Setup( ).Methods(http.MethodDelete) v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}/{attr}", - httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -707,7 +707,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}/{attr}", - httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -1022,19 +1022,19 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/notifications", - httputil.MakeAuthAPI("get_notifications", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_notifications", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetNotifications(req, device, userAPI) }), ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushers", - httputil.MakeAuthAPI("get_pushers", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_pushers", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetPushers(req, device, userAPI) }), ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushers/set", - httputil.MakeAuthAPI("set_pushers", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("set_pushers", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } diff --git a/setup/mscs/msc2946/msc2946.go b/setup/mscs/msc2946/msc2946.go index 7ab50c32ed..a19dbf3f08 100644 --- a/setup/mscs/msc2946/msc2946.go +++ b/setup/mscs/msc2946/msc2946.go @@ -57,7 +57,7 @@ func Enable( base *base.BaseDendrite, rsAPI roomserver.RoomserverInternalAPI, userAPI userapi.UserInternalAPI, fsAPI fs.FederationInternalAPI, keyRing gomatrixserverlib.JSONVerifier, cache caching.SpaceSummaryRoomsCache, ) error { - clientAPI := httputil.MakeAuthAPI("spaces", userAPI, spacesHandler(rsAPI, fsAPI, cache, base.Cfg.Global.ServerName)) + clientAPI := httputil.MakeAuthAPI("spaces", userAPI, base.Cfg.Global.UserConsentOptions, httputil.ConsentNotRequired, spacesHandler(rsAPI, fsAPI, cache, base.Cfg.Global.ServerName)) base.PublicClientAPIMux.Handle("/v1/rooms/{roomID}/hierarchy", clientAPI).Methods(http.MethodGet, http.MethodOptions) base.PublicClientAPIMux.Handle("/unstable/org.matrix.msc2946/rooms/{roomID}/hierarchy", clientAPI).Methods(http.MethodGet, http.MethodOptions) From e80ca307d386b90c81d2911eaab0d63bc41b4e33 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Fri, 4 Mar 2022 09:59:15 +0100 Subject: [PATCH 35/56] Fix receipts --- clientapi/routing/routing.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index 06d8807da9..13731af650 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -172,6 +172,7 @@ func Setup( // using ?: so the final regexp becomes what is below. We also need a trailing slash to stop 'v33333' matching. // Note that 'apiversion' is chosen because it must not collide with a variable used in any of the routing! v3mux := publicAPIMux.PathPrefix("/{apiversion:(?:r0|v3)}/").Subrouter() + unstableMux := publicAPIMux.PathPrefix("/unstable").Subrouter() // NOTSPEC: consent tracking if cfg.Matrix.UserConsentOptions.Enabled { @@ -188,9 +189,6 @@ func Setup( ).Methods(http.MethodGet, http.MethodPost, http.MethodOptions) } - r0mux := publicAPIMux.PathPrefix("/r0").Subrouter() - unstableMux := publicAPIMux.PathPrefix("/unstable").Subrouter() - v3mux.Handle("/createRoom", httputil.MakeAuthAPI("createRoom", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { return CreateRoom(req, device, cfg, accountDB, rsAPI, asAPI) @@ -1307,7 +1305,7 @@ func Setup( return ClaimKeys(req, keyAPI) }), ).Methods(http.MethodPost, http.MethodOptions) - r0mux.Handle("/rooms/{roomId}/receipt/{receiptType}/{eventId}", + v3mux.Handle("/rooms/{roomId}/receipt/{receiptType}/{eventId}", httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r From 519ea13510a7ba6183de9596dadc2037761b0530 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Fri, 4 Mar 2022 17:01:18 +0100 Subject: [PATCH 36/56] Add AuthAPICheck and optional functional checks Rename several variables --- clientapi/routing/consent_tracking.go | 48 ++--- clientapi/routing/routing.go | 285 +++++++++++++------------- internal/httputil/httpapi.go | 57 ++++-- setup/mscs/msc2946/msc2946.go | 2 +- syncapi/routing/routing.go | 14 +- 5 files changed, 213 insertions(+), 193 deletions(-) diff --git a/clientapi/routing/consent_tracking.go b/clientapi/routing/consent_tracking.go index ec5b10a7eb..2ba5e6215a 100644 --- a/clientapi/routing/consent_tracking.go +++ b/clientapi/routing/consent_tracking.go @@ -36,11 +36,11 @@ import ( // The data used to populate the /consent request type constentTemplateData struct { - User string - Version string - UserHMAC string - HasConsented bool - PublicVersion bool + UserID string + Version string + UserHMAC string + HasConsented bool + ReadOnly bool } func consent(writer http.ResponseWriter, req *http.Request, userAPI userapi.UserInternalAPI, cfg *config.ClientAPI) *util.JSONResponse { @@ -49,27 +49,27 @@ func consent(writer http.ResponseWriter, req *http.Request, userAPI userapi.User // The data used to populate the /consent request data := constentTemplateData{ - User: req.FormValue("u"), + UserID: req.FormValue("u"), Version: req.FormValue("v"), UserHMAC: req.FormValue("h"), } switch req.Method { case http.MethodGet: // display the privacy policy without a form - data.PublicVersion = data.User == "" || data.UserHMAC == "" || data.Version == "" + data.ReadOnly = data.UserID == "" || data.UserHMAC == "" || data.Version == "" // let's see if the user already consented to the current version - if !data.PublicVersion { + if !data.ReadOnly { res := &userapi.QueryPolicyVersionResponse{} - localPart, _, err := gomatrixserverlib.SplitID('@', data.User) + localpart, _, err := gomatrixserverlib.SplitID('@', data.UserID) if err != nil { - logrus.WithError(err).Error("unable to print consent template") + logrus.WithError(err).Error("unable to split username") return &internalError } if err = userAPI.QueryPolicyVersion(req.Context(), &userapi.QueryPolicyVersionRequest{ - LocalPart: localPart, + Localpart: localpart, }, res); err != nil { - logrus.WithError(err).Error("unable to print consent template") + logrus.WithError(err).Error("unable query policy version") return &internalError } data.HasConsented = res.PolicyVersion == consentCfg.Version @@ -77,18 +77,18 @@ func consent(writer http.ResponseWriter, req *http.Request, userAPI userapi.User err := consentCfg.Templates.ExecuteTemplate(writer, consentCfg.Version+".gohtml", data) if err != nil { - logrus.WithError(err).Error("unable to print consent template") + logrus.WithError(err).Error("unable to execute consent template") return nil } return nil case http.MethodPost: - localPart, _, err := gomatrixserverlib.SplitID('@', data.User) + localpart, _, err := gomatrixserverlib.SplitID('@', data.UserID) if err != nil { logrus.WithError(err).Error("unable to split username") return &internalError } - ok, err := validHMAC(data.User, data.UserHMAC, consentCfg.FormSecret) + ok, err := validHMAC(data.UserID, data.UserHMAC, consentCfg.FormSecret) if err != nil || !ok { _, err = writer.Write([]byte("invalid HMAC provided")) if err != nil { @@ -100,7 +100,7 @@ func consent(writer http.ResponseWriter, req *http.Request, userAPI userapi.User req.Context(), &userapi.UpdatePolicyVersionRequest{ PolicyVersion: data.Version, - LocalPart: localPart, + Localpart: localpart, }, &userapi.UpdatePolicyVersionResponse{}, ); err != nil { @@ -111,7 +111,7 @@ func consent(writer http.ResponseWriter, req *http.Request, userAPI userapi.User return &internalError } // display the privacy policy without a form - data.PublicVersion = false + data.ReadOnly = false data.HasConsented = true err = consentCfg.Templates.ExecuteTemplate(writer, consentCfg.Version+".gohtml", data) @@ -146,15 +146,17 @@ func sendServerNoticeForConsent(userAPI userapi.UserInternalAPI, rsAPI api.Rooms sentMessages int ) - if len(res.OutdatedUsers) > 0 { - logrus.WithField("count", len(res.OutdatedUsers)).Infof("Sending server notice to users who have not yet accepted the policy") + if len(res.UserLocalparts) == 0 { + return } - for _, userID := range res.OutdatedUsers { - if userID == cfgClient.Matrix.ServerNotices.LocalPart { + logrus.WithField("count", len(res.UserLocalparts)).Infof("Sending server notice to users who have not yet accepted the policy") + + for _, localpart := range res.UserLocalparts { + if localpart == cfgClient.Matrix.ServerNotices.LocalPart { continue } - userID = fmt.Sprintf("@%s:%s", userID, cfgClient.Matrix.ServerName) + userID := fmt.Sprintf("@%s:%s", localpart, cfgClient.Matrix.ServerName) data["ConsentURL"], err = buildConsentURI(cfgClient, userID) if err != nil { logrus.WithError(err).WithField("userID", userID).Error("unable to construct consentURI") @@ -186,7 +188,7 @@ func sendServerNoticeForConsent(userAPI userapi.UserInternalAPI, rsAPI api.Rooms res := &userapi.UpdatePolicyVersionResponse{} if err = userAPI.PerformUpdatePolicyVersion(context.Background(), &userapi.UpdatePolicyVersionRequest{ PolicyVersion: consentOpts.Version, - LocalPart: userID, + Localpart: userID, ServerNoticeUpdate: true, }, res); err != nil { logrus.WithError(err).WithField("userID", userID).Error("failed to update policy version") diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index 13731af650..705a93d9bc 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -130,7 +130,7 @@ func Setup( } synapseAdminRouter.Handle("/admin/v1/send_server_notice/{txnID}", - httputil.MakeAuthAPI("send_server_notice", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_server_notice", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { // not specced, but ensure we're rate limiting requests to this endpoint if r := rateLimits.Limit(req); r != nil { return *r @@ -150,7 +150,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) synapseAdminRouter.Handle("/admin/v1/send_server_notice", - httputil.MakeAuthAPI("send_server_notice", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_server_notice", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { // not specced, but ensure we're rate limiting requests to this endpoint if r := rateLimits.Limit(req); r != nil { return *r @@ -189,13 +189,16 @@ func Setup( ).Methods(http.MethodGet, http.MethodPost, http.MethodOptions) } + consentRequiredCheck := httputil.WithConsentCheck(cfg.Matrix.UserConsentOptions, userAPI) + v3mux.Handle("/createRoom", - httputil.MakeAuthAPI("createRoom", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + + httputil.MakeAuthAPI("createRoom", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return CreateRoom(req, device, cfg, accountDB, rsAPI, asAPI) - }), + }, consentRequiredCheck), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/join/{roomIDOrAlias}", - httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -211,7 +214,7 @@ func Setup( if mscCfg.Enabled("msc2753") { v3mux.Handle("/peek/{roomIDOrAlias}", - httputil.MakeAuthAPI(gomatrixserverlib.Peek, userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI(gomatrixserverlib.Peek, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -222,16 +225,16 @@ func Setup( return PeekRoomByIDOrAlias( req, device, rsAPI, accountDB, vars["roomIDOrAlias"], ) - }), + }, consentRequiredCheck), ).Methods(http.MethodPost, http.MethodOptions) } v3mux.Handle("/joined_rooms", - httputil.MakeAuthAPI("joined_rooms", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("joined_rooms", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetJoinedRooms(req, device, rsAPI) }), ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/join", - httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -245,7 +248,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/leave", - httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -259,7 +262,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/unpeek", - httputil.MakeAuthAPI("unpeek", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("unpeek", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -267,19 +270,19 @@ func Setup( return UnpeekRoomByID( req, device, rsAPI, accountDB, vars["roomID"], ) - }), + }, consentRequiredCheck), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/ban", - httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } return SendBan(req, accountDB, device, vars["roomID"], cfg, rsAPI, asAPI) - }), + }, consentRequiredCheck), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/invite", - httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -288,28 +291,28 @@ func Setup( return util.ErrorResponse(err) } return SendInvite(req, accountDB, device, vars["roomID"], cfg, rsAPI, asAPI) - }), + }, consentRequiredCheck), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/kick", - httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } return SendKick(req, accountDB, device, vars["roomID"], cfg, rsAPI, asAPI) - }), + }, consentRequiredCheck), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/unban", - httputil.MakeAuthAPI("membership", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } return SendUnban(req, accountDB, device, vars["roomID"], cfg, rsAPI, asAPI) - }), + }, consentRequiredCheck), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/send/{eventType}", - httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -318,7 +321,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/send/{eventType}/{txnID}", - httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -326,35 +329,35 @@ func Setup( txnID := vars["txnID"] return SendEvent(req, device, vars["roomID"], vars["eventType"], &txnID, nil, cfg, rsAPI, transactionsCache) - }), + }, consentRequiredCheck), ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/event/{eventID}", - httputil.MakeAuthAPI("rooms_get_event", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_get_event", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } return GetEvent(req, device, vars["roomID"], vars["eventID"], cfg, rsAPI, federation) - }), + }, consentRequiredCheck), ).Methods(http.MethodGet, http.MethodOptions) - v3mux.Handle("/rooms/{roomID}/state", httputil.MakeAuthAPI("room_state", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + v3mux.Handle("/rooms/{roomID}/state", httputil.MakeAuthAPI("room_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } return OnIncomingStateRequest(req.Context(), device, rsAPI, vars["roomID"]) - })).Methods(http.MethodGet, http.MethodOptions) + }, consentRequiredCheck)).Methods(http.MethodGet, http.MethodOptions) - v3mux.Handle("/rooms/{roomID}/aliases", httputil.MakeAuthAPI("aliases", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + v3mux.Handle("/rooms/{roomID}/aliases", httputil.MakeAuthAPI("aliases", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } return GetAliases(req, rsAPI, device, vars["roomID"]) - })).Methods(http.MethodGet, http.MethodOptions) + }, consentRequiredCheck)).Methods(http.MethodGet, http.MethodOptions) - v3mux.Handle("/rooms/{roomID}/state/{type:[^/]+/?}", httputil.MakeAuthAPI("room_state", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + v3mux.Handle("/rooms/{roomID}/state/{type:[^/]+/?}", httputil.MakeAuthAPI("room_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -363,19 +366,19 @@ func Setup( eventType := strings.TrimSuffix(vars["type"], "/") eventFormat := req.URL.Query().Get("format") == "event" return OnIncomingStateTypeRequest(req.Context(), device, rsAPI, vars["roomID"], eventType, "", eventFormat) - })).Methods(http.MethodGet, http.MethodOptions) + }, consentRequiredCheck)).Methods(http.MethodGet, http.MethodOptions) - v3mux.Handle("/rooms/{roomID}/state/{type}/{stateKey}", httputil.MakeAuthAPI("room_state", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + v3mux.Handle("/rooms/{roomID}/state/{type}/{stateKey}", httputil.MakeAuthAPI("room_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } eventFormat := req.URL.Query().Get("format") == "event" return OnIncomingStateTypeRequest(req.Context(), device, rsAPI, vars["roomID"], vars["type"], vars["stateKey"], eventFormat) - })).Methods(http.MethodGet, http.MethodOptions) + }, consentRequiredCheck)).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/state/{eventType:[^/]+/?}", - httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -383,18 +386,18 @@ func Setup( emptyString := "" eventType := strings.TrimSuffix(vars["eventType"], "/") return SendEvent(req, device, vars["roomID"], eventType, nil, &emptyString, cfg, rsAPI, nil) - }), + }, consentRequiredCheck), ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/state/{eventType}/{stateKey}", - httputil.MakeAuthAPI("send_message", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } stateKey := vars["stateKey"] return SendEvent(req, device, vars["roomID"], vars["eventType"], nil, &stateKey, cfg, rsAPI, nil) - }), + }, consentRequiredCheck), ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/register", httputil.MakeExternalAPI("register", func(req *http.Request) util.JSONResponse { @@ -422,7 +425,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/directory/room/{roomAlias}", - httputil.MakeAuthAPI("directory_room", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("directory_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -432,7 +435,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/directory/room/{roomAlias}", - httputil.MakeAuthAPI("directory_room", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("directory_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -451,7 +454,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) // TODO: Add AS support v3mux.Handle("/directory/list/room/{roomID}", - httputil.MakeAuthAPI("directory_list", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("directory_list", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -466,19 +469,19 @@ func Setup( ).Methods(http.MethodGet, http.MethodPost, http.MethodOptions) v3mux.Handle("/logout", - httputil.MakeAuthAPI("logout", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("logout", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return Logout(req, userAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/logout/all", - httputil.MakeAuthAPI("logout", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("logout", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return LogoutAll(req, userAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/typing/{userID}", - httputil.MakeAuthAPI("rooms_typing", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_typing", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -490,16 +493,16 @@ func Setup( }), ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/redact/{eventID}", - httputil.MakeAuthAPI("rooms_redact", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_redact", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } return SendRedaction(req, device, vars["roomID"], vars["eventID"], cfg, rsAPI) - }), + }, consentRequiredCheck), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/redact/{eventID}/{txnId}", - httputil.MakeAuthAPI("rooms_redact", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_redact", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -509,41 +512,41 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/sendToDevice/{eventType}/{txnID}", - httputil.MakeAuthAPI("send_to_device", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_to_device", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } txnID := vars["txnID"] return SendToDevice(req, device, eduAPI, transactionsCache, vars["eventType"], &txnID) - }), + }, consentRequiredCheck), ).Methods(http.MethodPut, http.MethodOptions) // This is only here because sytest refers to /unstable for this endpoint // rather than r0. It's an exact duplicate of the above handler. // TODO: Remove this if/when sytest is fixed! unstableMux.Handle("/sendToDevice/{eventType}/{txnID}", - httputil.MakeAuthAPI("send_to_device", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("send_to_device", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } txnID := vars["txnID"] return SendToDevice(req, device, eduAPI, transactionsCache, vars["eventType"], &txnID) - }), + }, consentRequiredCheck), ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/account/whoami", - httputil.MakeAuthAPI("whoami", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("whoami", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } return Whoami(req, device) - }), + }, consentRequiredCheck), ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/account/password", - httputil.MakeAuthAPI("password", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("password", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -552,7 +555,7 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/account/deactivate", - httputil.MakeAuthAPI("deactivate", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("deactivate", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -581,7 +584,7 @@ func Setup( // Push rules v3mux.Handle("/pushrules", - httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: jsonerror.InvalidArgumentValue("missing trailing slash"), @@ -590,13 +593,13 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushrules/", - httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetAllPushRules(req.Context(), device, userAPI) }), ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushrules/", - httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: jsonerror.InvalidArgumentValue("scope, kind and rule ID must be specified"), @@ -605,7 +608,7 @@ func Setup( ).Methods(http.MethodPut) v3mux.Handle("/pushrules/{scope}/", - httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -615,7 +618,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushrules/{scope}", - httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: jsonerror.InvalidArgumentValue("missing trailing slash after scope"), @@ -624,7 +627,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushrules/{scope:[^/]+/?}", - httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: jsonerror.InvalidArgumentValue("kind and rule ID must be specified"), @@ -633,7 +636,7 @@ func Setup( ).Methods(http.MethodPut) v3mux.Handle("/pushrules/{scope}/{kind}/", - httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -643,7 +646,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushrules/{scope}/{kind}", - httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: jsonerror.InvalidArgumentValue("missing trailing slash after kind"), @@ -652,7 +655,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushrules/{scope}/{kind:[^/]+/?}", - httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: jsonerror.InvalidArgumentValue("rule ID must be specified"), @@ -661,7 +664,7 @@ func Setup( ).Methods(http.MethodPut) v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}", - httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -671,7 +674,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}", - httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -685,7 +688,7 @@ func Setup( ).Methods(http.MethodPut) v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}", - httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -695,7 +698,7 @@ func Setup( ).Methods(http.MethodDelete) v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}/{attr}", - httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -705,7 +708,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}/{attr}", - httputil.MakeAuthAPI("push_rules", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -737,7 +740,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/profile/{userID}/avatar_url", - httputil.MakeAuthAPI("profile_avatar_url", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("profile_avatar_url", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -746,7 +749,7 @@ func Setup( return util.ErrorResponse(err) } return SetAvatarURL(req, accountDB, device, vars["userID"], cfg, rsAPI) - }), + }, consentRequiredCheck), ).Methods(http.MethodPut, http.MethodOptions) // Browsers use the OPTIONS HTTP method to check if the CORS policy allows // PUT requests, so we need to allow this method @@ -762,7 +765,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/profile/{userID}/displayname", - httputil.MakeAuthAPI("profile_displayname", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("profile_displayname", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -771,27 +774,27 @@ func Setup( return util.ErrorResponse(err) } return SetDisplayName(req, accountDB, device, vars["userID"], cfg, rsAPI) - }), + }, consentRequiredCheck), ).Methods(http.MethodPut, http.MethodOptions) // Browsers use the OPTIONS HTTP method to check if the CORS policy allows // PUT requests, so we need to allow this method v3mux.Handle("/account/3pid", - httputil.MakeAuthAPI("account_3pid", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("account_3pid", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetAssociated3PIDs(req, accountDB, device) - }), + }, consentRequiredCheck), ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/account/3pid", - httputil.MakeAuthAPI("account_3pid", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("account_3pid", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return CheckAndSave3PIDAssociation(req, accountDB, device, cfg) - }), + }, consentRequiredCheck), ).Methods(http.MethodPost, http.MethodOptions) unstableMux.Handle("/account/3pid/delete", - httputil.MakeAuthAPI("account_3pid", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("account_3pid", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return Forget3PID(req, accountDB) - }), + }, consentRequiredCheck), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/{path:(?:account/3pid|register)}/email/requestToken", @@ -815,12 +818,12 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/voip/turnServer", - httputil.MakeAuthAPI("turn_server", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("turn_server", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } return RequestTurnServer(req, device, cfg) - }), + }, consentRequiredCheck), ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/thirdparty/protocols", @@ -844,7 +847,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/user/{userID}/account_data/{type}", - httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("user_account_data", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -854,7 +857,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/user/{userID}/rooms/{roomID}/account_data/{type}", - httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("user_account_data", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -864,7 +867,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/user/{userID}/account_data/{type}", - httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("user_account_data", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -874,7 +877,7 @@ func Setup( ).Methods(http.MethodGet) v3mux.Handle("/user/{userID}/rooms/{roomID}/account_data/{type}", - httputil.MakeAuthAPI("user_account_data", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("user_account_data", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -884,17 +887,17 @@ func Setup( ).Methods(http.MethodGet) v3mux.Handle("/admin/whois/{userID}", - httputil.MakeAuthAPI("admin_whois", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("admin_whois", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } return GetAdminWhois(req, userAPI, device, vars["userID"]) - }), + }, consentRequiredCheck), ).Methods(http.MethodGet) v3mux.Handle("/user/{userID}/openid/request_token", - httputil.MakeAuthAPI("openid_request_token", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("openid_request_token", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -903,11 +906,11 @@ func Setup( return util.ErrorResponse(err) } return CreateOpenIDToken(req, userAPI, device, vars["userID"], cfg) - }), + }, consentRequiredCheck), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/user_directory/search", - httputil.MakeAuthAPI("userdirectory_search", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("userdirectory_search", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -928,11 +931,11 @@ func Setup( postContent.SearchString, postContent.Limit, ) - }), + }, consentRequiredCheck), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/members", - httputil.MakeAuthAPI("rooms_members", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_members", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -942,7 +945,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/joined_members", - httputil.MakeAuthAPI("rooms_members", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_members", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -952,7 +955,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/read_markers", - httputil.MakeAuthAPI("rooms_read_markers", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_read_markers", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -965,7 +968,7 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomID}/forget", - httputil.MakeAuthAPI("rooms_forget", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("rooms_forget", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -974,17 +977,17 @@ func Setup( return util.ErrorResponse(err) } return SendForget(req, device, vars["roomID"], rsAPI) - }), + }, consentRequiredCheck), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/devices", - httputil.MakeAuthAPI("get_devices", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_devices", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetDevicesByLocalpart(req, userAPI, device) }), ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/devices/{deviceID}", - httputil.MakeAuthAPI("get_device", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_device", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -994,7 +997,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/devices/{deviceID}", - httputil.MakeAuthAPI("device_data", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("device_data", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -1004,7 +1007,7 @@ func Setup( ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/devices/{deviceID}", - httputil.MakeAuthAPI("delete_device", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("delete_device", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -1014,25 +1017,25 @@ func Setup( ).Methods(http.MethodDelete, http.MethodOptions) v3mux.Handle("/delete_devices", - httputil.MakeAuthAPI("delete_devices", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("delete_devices", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return DeleteDevices(req, userAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/notifications", - httputil.MakeAuthAPI("get_notifications", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_notifications", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetNotifications(req, device, userAPI) }), ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushers", - httputil.MakeAuthAPI("get_pushers", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_pushers", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetPushers(req, device, userAPI) }), ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/pushers/set", - httputil.MakeAuthAPI("set_pushers", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("set_pushers", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -1060,7 +1063,7 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/user/{userId}/rooms/{roomId}/tags", - httputil.MakeAuthAPI("get_tags", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_tags", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -1070,27 +1073,27 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/user/{userId}/rooms/{roomId}/tags/{tag}", - httputil.MakeAuthAPI("put_tag", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("put_tag", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } return PutTag(req, userAPI, device, vars["userId"], vars["roomId"], vars["tag"], syncProducer) - }), + }, consentRequiredCheck), ).Methods(http.MethodPut, http.MethodOptions) v3mux.Handle("/user/{userId}/rooms/{roomId}/tags/{tag}", - httputil.MakeAuthAPI("delete_tag", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("delete_tag", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } return DeleteTag(req, userAPI, device, vars["userId"], vars["roomId"], vars["tag"], syncProducer) - }), + }, consentRequiredCheck), ).Methods(http.MethodDelete, http.MethodOptions) v3mux.Handle("/capabilities", - httputil.MakeAuthAPI("capabilities", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("capabilities", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -1100,27 +1103,27 @@ func Setup( // Key Backup Versions (Metadata) - getBackupKeysVersion := httputil.MakeAuthAPI("get_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getBackupKeysVersion := httputil.MakeAuthAPI("get_backup_keys_version", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } return KeyBackupVersion(req, userAPI, device, vars["version"]) - }) + }, consentRequiredCheck) - getLatestBackupKeysVersion := httputil.MakeAuthAPI("get_latest_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getLatestBackupKeysVersion := httputil.MakeAuthAPI("get_latest_backup_keys_version", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return KeyBackupVersion(req, userAPI, device, "") - }) + }, consentRequiredCheck) - putBackupKeysVersion := httputil.MakeAuthAPI("put_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + putBackupKeysVersion := httputil.MakeAuthAPI("put_backup_keys_version", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } return ModifyKeyBackupVersionAuthData(req, userAPI, device, vars["version"]) - }) + }, consentRequiredCheck) - deleteBackupKeysVersion := httputil.MakeAuthAPI("delete_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + deleteBackupKeysVersion := httputil.MakeAuthAPI("delete_backup_keys_version", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -1128,9 +1131,9 @@ func Setup( return DeleteKeyBackupVersion(req, userAPI, device, vars["version"]) }) - postNewBackupKeysVersion := httputil.MakeAuthAPI("post_new_backup_keys_version", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + postNewBackupKeysVersion := httputil.MakeAuthAPI("post_new_backup_keys_version", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return CreateKeyBackupVersion(req, userAPI, device) - }) + }, consentRequiredCheck) v3mux.Handle("/room_keys/version/{version}", getBackupKeysVersion).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/room_keys/version", getLatestBackupKeysVersion).Methods(http.MethodGet, http.MethodOptions) @@ -1147,7 +1150,7 @@ func Setup( // Inserting E2E Backup Keys // Bulk room and session - putBackupKeys := httputil.MakeAuthAPI("put_backup_keys", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + putBackupKeys := httputil.MakeAuthAPI("put_backup_keys", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { version := req.URL.Query().Get("version") if version == "" { return util.JSONResponse{ @@ -1161,10 +1164,10 @@ func Setup( return *resErr } return UploadBackupKeys(req, userAPI, device, version, &reqBody) - }) + }, consentRequiredCheck) // Single room bulk session - putBackupKeysRoom := httputil.MakeAuthAPI("put_backup_keys_room", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + putBackupKeysRoom := httputil.MakeAuthAPI("put_backup_keys_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -1193,10 +1196,10 @@ func Setup( } reqBody.Rooms[roomID] = body return UploadBackupKeys(req, userAPI, device, version, &reqBody) - }) + }, consentRequiredCheck) // Single room, single session - putBackupKeysRoomSession := httputil.MakeAuthAPI("put_backup_keys_room_session", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + putBackupKeysRoomSession := httputil.MakeAuthAPI("put_backup_keys_room_session", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -1226,7 +1229,7 @@ func Setup( } keyReq.Rooms[roomID].Sessions[sessionID] = reqBody return UploadBackupKeys(req, userAPI, device, version, &keyReq) - }) + }, consentRequiredCheck) v3mux.Handle("/room_keys/keys", putBackupKeys).Methods(http.MethodPut) v3mux.Handle("/room_keys/keys/{roomID}", putBackupKeysRoom).Methods(http.MethodPut) @@ -1238,11 +1241,11 @@ func Setup( // Querying E2E Backup Keys - getBackupKeys := httputil.MakeAuthAPI("get_backup_keys", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getBackupKeys := httputil.MakeAuthAPI("get_backup_keys", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetBackupKeys(req, userAPI, device, req.URL.Query().Get("version"), "", "") - }) + }, consentRequiredCheck) - getBackupKeysRoom := httputil.MakeAuthAPI("get_backup_keys_room", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getBackupKeysRoom := httputil.MakeAuthAPI("get_backup_keys_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -1250,13 +1253,13 @@ func Setup( return GetBackupKeys(req, userAPI, device, req.URL.Query().Get("version"), vars["roomID"], "") }) - getBackupKeysRoomSession := httputil.MakeAuthAPI("get_backup_keys_room_session", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + getBackupKeysRoomSession := httputil.MakeAuthAPI("get_backup_keys_room_session", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } return GetBackupKeys(req, userAPI, device, req.URL.Query().Get("version"), vars["roomID"], vars["sessionID"]) - }) + }, consentRequiredCheck) v3mux.Handle("/room_keys/keys", getBackupKeys).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/room_keys/keys/{roomID}", getBackupKeysRoom).Methods(http.MethodGet, http.MethodOptions) @@ -1270,13 +1273,13 @@ func Setup( // Cross-signing device keys - postDeviceSigningKeys := httputil.MakeAuthAPI("post_device_signing_keys", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + postDeviceSigningKeys := httputil.MakeAuthAPI("post_device_signing_keys", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return UploadCrossSigningDeviceKeys(req, userInteractiveAuth, keyAPI, device, accountDB, cfg) - }) + }, consentRequiredCheck) - postDeviceSigningSignatures := httputil.MakeAuthAPI("post_device_signing_signatures", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + postDeviceSigningSignatures := httputil.MakeAuthAPI("post_device_signing_signatures", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return UploadCrossSigningDeviceSignatures(req, keyAPI, device) - }) + }, consentRequiredCheck) v3mux.Handle("/keys/device_signing/upload", postDeviceSigningKeys).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/keys/signatures/upload", postDeviceSigningSignatures).Methods(http.MethodPost, http.MethodOptions) @@ -1286,27 +1289,27 @@ func Setup( // Supplying a device ID is deprecated. v3mux.Handle("/keys/upload/{deviceID}", - httputil.MakeAuthAPI("keys_upload", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("keys_upload", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return UploadKeys(req, keyAPI, device) - }), + }, consentRequiredCheck), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/keys/upload", - httputil.MakeAuthAPI("keys_upload", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("keys_upload", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return UploadKeys(req, keyAPI, device) - }), + }, consentRequiredCheck), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/keys/query", - httputil.MakeAuthAPI("keys_query", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("keys_query", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return QueryKeys(req, keyAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/keys/claim", - httputil.MakeAuthAPI("keys_claim", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("keys_claim", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return ClaimKeys(req, keyAPI) }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/rooms/{roomId}/receipt/{receiptType}/{eventId}", - httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } @@ -1316,6 +1319,6 @@ func Setup( } return SetReceipt(req, eduAPI, device, vars["roomId"], vars["receiptType"], vars["eventId"]) - }), + }, consentRequiredCheck), ).Methods(http.MethodPost, http.MethodOptions) } diff --git a/internal/httputil/httpapi.go b/internal/httputil/httpapi.go index 091c36c3b1..910d928766 100644 --- a/internal/httputil/httpapi.go +++ b/internal/httputil/httpapi.go @@ -53,20 +53,25 @@ type BasicAuth struct { Password string `yaml:"password"` } -type Consent bool +// AuthAPICheck is an option to MakeAuthAPI to add additional checks (e.g. WithConsentCheck) to verify +// the user is allowed to do specific things. +type AuthAPICheck func(ctx context.Context, device *userapi.Device) *util.JSONResponse -const ( - ConsentRequired Consent = true - ConsentNotRequired Consent = false -) +// WithConsentCheck checks that a user has given his consent. +func WithConsentCheck(options config.UserConsentOptions, api userapi.UserInternalAPI) AuthAPICheck { + return func(ctx context.Context, device *userapi.Device) *util.JSONResponse { + if !options.Enabled { + return nil + } + return checkConsent(ctx, device.UserID, api, options) + } +} // MakeAuthAPI turns a util.JSONRequestHandler function into an http.Handler which authenticates the request. func MakeAuthAPI( metricsName string, userAPI userapi.UserInternalAPI, - userConsentCfg config.UserConsentOptions, - requireConsent Consent, - f func(*http.Request, *userapi.Device) util.JSONResponse, + f func(*http.Request, *userapi.Device) util.JSONResponse, checks ...AuthAPICheck, ) http.Handler { h := func(req *http.Request) util.JSONResponse { logger := util.GetLogger(req.Context()) @@ -94,13 +99,11 @@ func MakeAuthAPI( } }() - if userConsentCfg.Enabled && requireConsent == ConsentRequired { - consentError := checkConsent(req.Context(), device.UserID, userAPI, userConsentCfg) - if consentError != nil { - return util.JSONResponse{ - Code: http.StatusForbidden, - JSON: consentError, - } + // apply additional checks, if any + for _, opt := range checks { + resp := opt(req.Context(), device) + if resp != nil { + return *resp } } @@ -115,7 +118,7 @@ func MakeAuthAPI( return MakeExternalAPI(metricsName, h) } -func checkConsent(ctx context.Context, userID string, userAPI userapi.UserInternalAPI, userConsentCfg config.UserConsentOptions) error { +func checkConsent(ctx context.Context, userID string, userAPI userapi.UserInternalAPI, userConsentCfg config.UserConsentOptions) *util.JSONResponse { localPart, _, err := gomatrixserverlib.SplitID('@', userID) if err != nil { return nil @@ -123,17 +126,23 @@ func checkConsent(ctx context.Context, userID string, userAPI userapi.UserIntern // check which version of the policy the user accepted res := &userapi.QueryPolicyVersionResponse{} err = userAPI.QueryPolicyVersion(ctx, &userapi.QueryPolicyVersionRequest{ - LocalPart: localPart, + Localpart: localPart, }, res) if err != nil { - return nil + return &util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: jsonerror.Unknown("unable to get policy version"), + } } // user hasn't accepted any policy, block access. if userConsentCfg.Version != res.PolicyVersion { uri, err := getConsentURL(userID, userConsentCfg) if err != nil { - return jsonerror.Unknown("unable to get consent URL") + return &util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: jsonerror.Unknown("unable to get consent URL"), + } } msg := &bytes.Buffer{} c := struct { @@ -143,9 +152,15 @@ func checkConsent(ctx context.Context, userID string, userAPI userapi.UserIntern } if err = userConsentCfg.TextTemplates.ExecuteTemplate(msg, "blockEventsError", c); err != nil { logrus.Infof("error consent message: %+v", err) - return jsonerror.Unknown("unable to get consent URL") + return &util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: jsonerror.Unknown("unable to execute template"), + } + } + return &util.JSONResponse{ + Code: http.StatusForbidden, + JSON: jsonerror.ConsentNotGiven(uri, msg.String()), } - return jsonerror.ConsentNotGiven(uri, msg.String()) } return nil } diff --git a/setup/mscs/msc2946/msc2946.go b/setup/mscs/msc2946/msc2946.go index a19dbf3f08..7ab50c32ed 100644 --- a/setup/mscs/msc2946/msc2946.go +++ b/setup/mscs/msc2946/msc2946.go @@ -57,7 +57,7 @@ func Enable( base *base.BaseDendrite, rsAPI roomserver.RoomserverInternalAPI, userAPI userapi.UserInternalAPI, fsAPI fs.FederationInternalAPI, keyRing gomatrixserverlib.JSONVerifier, cache caching.SpaceSummaryRoomsCache, ) error { - clientAPI := httputil.MakeAuthAPI("spaces", userAPI, base.Cfg.Global.UserConsentOptions, httputil.ConsentNotRequired, spacesHandler(rsAPI, fsAPI, cache, base.Cfg.Global.ServerName)) + clientAPI := httputil.MakeAuthAPI("spaces", userAPI, spacesHandler(rsAPI, fsAPI, cache, base.Cfg.Global.ServerName)) base.PublicClientAPIMux.Handle("/v1/rooms/{roomID}/hierarchy", clientAPI).Methods(http.MethodGet, http.MethodOptions) base.PublicClientAPIMux.Handle("/unstable/org.matrix.msc2946/rooms/{roomID}/hierarchy", clientAPI).Methods(http.MethodGet, http.MethodOptions) diff --git a/syncapi/routing/routing.go b/syncapi/routing/routing.go index 700a56201b..8e7d0627bf 100644 --- a/syncapi/routing/routing.go +++ b/syncapi/routing/routing.go @@ -42,11 +42,11 @@ func Setup( v3mux := csMux.PathPrefix("/{apiversion:(?:r0|v3)}/").Subrouter() // TODO: Add AS support for all handlers below. - v3mux.Handle("/sync", httputil.MakeAuthAPI("sync", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + v3mux.Handle("/sync", httputil.MakeAuthAPI("sync", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return srp.OnIncomingSyncRequest(req, device) })).Methods(http.MethodGet, http.MethodOptions) - v3mux.Handle("/rooms/{roomID}/messages", httputil.MakeAuthAPI("room_messages", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + v3mux.Handle("/rooms/{roomID}/messages", httputil.MakeAuthAPI("room_messages", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -55,7 +55,7 @@ func Setup( })).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/user/{userId}/filter", - httputil.MakeAuthAPI("put_filter", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("put_filter", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -65,7 +65,7 @@ func Setup( ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/user/{userId}/filter/{filterId}", - httputil.MakeAuthAPI("get_filter", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI("get_filter", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -74,12 +74,12 @@ func Setup( }), ).Methods(http.MethodGet, http.MethodOptions) - v3mux.Handle("/keys/changes", httputil.MakeAuthAPI("keys_changes", userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentNotRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + v3mux.Handle("/keys/changes", httputil.MakeAuthAPI("keys_changes", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return srp.OnIncomingKeyChangeRequest(req, device) })).Methods(http.MethodGet, http.MethodOptions) v3mux.Handle("/rooms/{roomId}/context/{eventId}", - httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, cfg.Matrix.UserConsentOptions, httputil.ConsentRequired, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -90,6 +90,6 @@ func Setup( rsAPI, syncDB, vars["roomId"], vars["eventId"], ) - }), + }, httputil.WithConsentCheck(cfg.Matrix.UserConsentOptions, userAPI)), ).Methods(http.MethodGet, http.MethodOptions) } From 699617ee4d13690d8761c0eec0affb050595e59f Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 7 Mar 2022 09:41:25 +0100 Subject: [PATCH 37/56] Add server_notice_room_id and methods to update/get it --- userapi/api/api.go | 30 ++++++-- userapi/api/api_trace.go | 12 ++++ userapi/internal/api.go | 27 +++++++- userapi/inthttp/client.go | 68 ++++++++++++------- userapi/inthttp/server.go | 28 ++++++++ userapi/storage/interface.go | 2 + userapi/storage/postgres/accounts_table.go | 53 +++++++++++++-- .../2022021414375800_add_policy_version.go | 15 +++- userapi/storage/shared/storage.go | 13 ++++ userapi/storage/sqlite3/accounts_table.go | 53 +++++++++++++-- .../2022021414375800_add_policy_version.go | 15 +++- userapi/storage/tables/interface.go | 2 + 12 files changed, 267 insertions(+), 51 deletions(-) diff --git a/userapi/api/api.go b/userapi/api/api.go index 244d13bb1b..96a6164e4f 100644 --- a/userapi/api/api.go +++ b/userapi/api/api.go @@ -57,6 +57,8 @@ type UserInternalAPI interface { QueryPolicyVersion(ctx context.Context, req *QueryPolicyVersionRequest, res *QueryPolicyVersionResponse) error QueryOutdatedPolicy(ctx context.Context, req *QueryOutdatedPolicyRequest, res *QueryOutdatedPolicyResponse) error PerformUpdatePolicyVersion(ctx context.Context, req *UpdatePolicyVersionRequest, res *UpdatePolicyVersionResponse) error + SelectServerNoticeRoomID(ctx context.Context, req *QueryServerNoticeRoomRequest, res *QueryServerNoticeRoomResponse) (err error) + UpdateServerNoticeRoomID(ctx context.Context, req *UpdateServerNoticeRoomRequest, res *UpdateServerNoticeRoomResponse) (err error) } type PerformKeyBackupRequest struct { @@ -348,12 +350,12 @@ type QueryOpenIDTokenResponse struct { ExpiresAtMS int64 } -// QueryPolicyVersionRequest is the response for QueryPolicyVersionRequest +// QueryPolicyVersionRequest is the request for QueryPolicyVersionRequest type QueryPolicyVersionRequest struct { - LocalPart string + Localpart string } -// QueryPolicyVersionResponsestruct is the response for QueryPolicyVersionResponsestruct +// QueryPolicyVersionResponse is the response for QueryPolicyVersionRequest type QueryPolicyVersionResponse struct { PolicyVersion string } @@ -365,18 +367,36 @@ type QueryOutdatedPolicyRequest struct { // QueryOutdatedPolicyResponse is the response for QueryOutdatedPolicyRequest type QueryOutdatedPolicyResponse struct { - OutdatedUsers []string + UserLocalparts []string } // UpdatePolicyVersionRequest is the request for UpdatePolicyVersionRequest type UpdatePolicyVersionRequest struct { - PolicyVersion, LocalPart string + PolicyVersion, Localpart string ServerNoticeUpdate bool } // UpdatePolicyVersionResponse is the response for UpdatePolicyVersionRequest type UpdatePolicyVersionResponse struct{} +// QueryServerNoticeRoomRequest is the request for QueryServerNoticeRoomRequest +type QueryServerNoticeRoomRequest struct { + Localpart string +} + +// QueryServerNoticeRoomResponse is the response for QueryServerNoticeRoomRequest +type QueryServerNoticeRoomResponse struct { + RoomID string +} + +// UpdateServerNoticeRoomRequest is the request for UpdateServerNoticeRoomRequest +type UpdateServerNoticeRoomRequest struct { + Localpart, RoomID string +} + +// UpdateServerNoticeRoomResponse is the response for UpdateServerNoticeRoomRequest +type UpdateServerNoticeRoomResponse struct{} + // Device represents a client's device (mobile, web, etc) type Device struct { ID string diff --git a/userapi/api/api_trace.go b/userapi/api/api_trace.go index b8bde43405..9fbdde616d 100644 --- a/userapi/api/api_trace.go +++ b/userapi/api/api_trace.go @@ -167,6 +167,18 @@ func (t *UserInternalAPITrace) PerformUpdatePolicyVersion(ctx context.Context, r return err } +func (t *UserInternalAPITrace) SelectServerNoticeRoomID(ctx context.Context, req *QueryServerNoticeRoomRequest, res *QueryServerNoticeRoomResponse) error { + err := t.Impl.SelectServerNoticeRoomID(ctx, req, res) + util.GetLogger(ctx).Infof("SelectServerNoticeRoomID req=%+v res=%+v", js(req), js(res)) + return err +} + +func (t *UserInternalAPITrace) UpdateServerNoticeRoomID(ctx context.Context, req *UpdateServerNoticeRoomRequest, res *UpdateServerNoticeRoomResponse) error { + err := t.Impl.UpdateServerNoticeRoomID(ctx, req, res) + util.GetLogger(ctx).Infof("UpdateServerNoticeRoomID req=%+v res=%+v", js(req), js(res)) + return err +} + func js(thing interface{}) string { b, err := json.Marshal(thing) if err != nil { diff --git a/userapi/internal/api.go b/userapi/internal/api.go index dafbf21807..fd1ecd459a 100644 --- a/userapi/internal/api.go +++ b/userapi/internal/api.go @@ -769,7 +769,7 @@ func (a *UserInternalAPI) QueryPolicyVersion( res *api.QueryPolicyVersionResponse, ) error { var err error - res.PolicyVersion, err = a.DB.GetPrivacyPolicy(ctx, req.LocalPart) + res.PolicyVersion, err = a.DB.GetPrivacyPolicy(ctx, req.Localpart) if err != nil { return err } @@ -783,7 +783,7 @@ func (a *UserInternalAPI) QueryOutdatedPolicy( res *api.QueryOutdatedPolicyResponse, ) error { var err error - res.OutdatedUsers, err = a.DB.GetOutdatedPolicy(ctx, req.PolicyVersion) + res.UserLocalparts, err = a.DB.GetOutdatedPolicy(ctx, req.PolicyVersion) if err != nil { return err } @@ -796,5 +796,26 @@ func (a *UserInternalAPI) PerformUpdatePolicyVersion( req *api.UpdatePolicyVersionRequest, res *api.UpdatePolicyVersionResponse, ) error { - return a.DB.UpdatePolicyVersion(ctx, req.PolicyVersion, req.LocalPart, req.ServerNoticeUpdate) + return a.DB.UpdatePolicyVersion(ctx, req.PolicyVersion, req.Localpart, req.ServerNoticeUpdate) } + +func (a *UserInternalAPI) SelectServerNoticeRoomID( + ctx context.Context, + req *api.QueryServerNoticeRoomRequest, + res *api.QueryServerNoticeRoomResponse, +) (err error) { + roomID, err := a.DB.SelectServerNoticeRoomID(ctx, req.Localpart) + if err != nil { + return err + } + res.RoomID = roomID + return nil +} + +func (a *UserInternalAPI) UpdateServerNoticeRoomID( + ctx context.Context, + req *api.UpdateServerNoticeRoomRequest, + res *api.UpdateServerNoticeRoomResponse, +) (err error) { + return a.DB.UpdateServerNoticeRoomID(ctx, req.Localpart, req.RoomID) +} \ No newline at end of file diff --git a/userapi/inthttp/client.go b/userapi/inthttp/client.go index 59be975cda..4609c2e1a7 100644 --- a/userapi/inthttp/client.go +++ b/userapi/inthttp/client.go @@ -28,33 +28,35 @@ import ( const ( InputAccountDataPath = "/userapi/inputAccountData" - PerformDeviceCreationPath = "/userapi/performDeviceCreation" - PerformAccountCreationPath = "/userapi/performAccountCreation" - PerformPasswordUpdatePath = "/userapi/performPasswordUpdate" - PerformDeviceDeletionPath = "/userapi/performDeviceDeletion" - PerformLastSeenUpdatePath = "/userapi/performLastSeenUpdate" - PerformDeviceUpdatePath = "/userapi/performDeviceUpdate" - PerformAccountDeactivationPath = "/userapi/performAccountDeactivation" - PerformOpenIDTokenCreationPath = "/userapi/performOpenIDTokenCreation" - PerformKeyBackupPath = "/userapi/performKeyBackup" - PerformPusherSetPath = "/pushserver/performPusherSet" - PerformPusherDeletionPath = "/pushserver/performPusherDeletion" - PerformPushRulesPutPath = "/pushserver/performPushRulesPut" - PerformUpdatePolicyVersionPath = "/userapi/performUpdatePolicyVersion" - - QueryKeyBackupPath = "/userapi/queryKeyBackup" - QueryProfilePath = "/userapi/queryProfile" - QueryAccessTokenPath = "/userapi/queryAccessToken" - QueryDevicesPath = "/userapi/queryDevices" - QueryAccountDataPath = "/userapi/queryAccountData" - QueryDeviceInfosPath = "/userapi/queryDeviceInfos" - QuerySearchProfilesPath = "/userapi/querySearchProfiles" - QueryOpenIDTokenPath = "/userapi/queryOpenIDToken" - QueryPushersPath = "/pushserver/queryPushers" - QueryPushRulesPath = "/pushserver/queryPushRules" - QueryNotificationsPath = "/pushserver/queryNotifications" + PerformDeviceCreationPath = "/userapi/performDeviceCreation" + PerformAccountCreationPath = "/userapi/performAccountCreation" + PerformPasswordUpdatePath = "/userapi/performPasswordUpdate" + PerformDeviceDeletionPath = "/userapi/performDeviceDeletion" + PerformLastSeenUpdatePath = "/userapi/performLastSeenUpdate" + PerformDeviceUpdatePath = "/userapi/performDeviceUpdate" + PerformAccountDeactivationPath = "/userapi/performAccountDeactivation" + PerformOpenIDTokenCreationPath = "/userapi/performOpenIDTokenCreation" + PerformKeyBackupPath = "/userapi/performKeyBackup" + PerformPusherSetPath = "/pushserver/performPusherSet" + PerformPusherDeletionPath = "/pushserver/performPusherDeletion" + PerformPushRulesPutPath = "/pushserver/performPushRulesPut" + PerformUpdatePolicyVersionPath = "/userapi/performUpdatePolicyVersion" + PerformUpdateServerNoticeRoomPath = "/userapi/performUpdateServerNoticeRoom" + + QueryKeyBackupPath = "/userapi/queryKeyBackup" + QueryProfilePath = "/userapi/queryProfile" + QueryAccessTokenPath = "/userapi/queryAccessToken" + QueryDevicesPath = "/userapi/queryDevices" + QueryAccountDataPath = "/userapi/queryAccountData" + QueryDeviceInfosPath = "/userapi/queryDeviceInfos" + QuerySearchProfilesPath = "/userapi/querySearchProfiles" + QueryOpenIDTokenPath = "/userapi/queryOpenIDToken" + QueryPushersPath = "/pushserver/queryPushers" + QueryPushRulesPath = "/pushserver/queryPushRules" + QueryNotificationsPath = "/pushserver/queryNotifications" QueryPolicyVersionPath = "/userapi/queryPolicyVersion" QueryOutdatedPolicyUsersPath = "/userapi/queryOutdatedPolicy" + QueryServerNoticeRoomPath = "/userapi/queryServerNoticeRoom" ) // NewUserAPIClient creates a UserInternalAPI implemented by talking to a HTTP POST API. @@ -337,3 +339,19 @@ func (h *httpUserInternalAPI) PerformUpdatePolicyVersion(ctx context.Context, re apiURL := h.apiURL + PerformUpdatePolicyVersionPath return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) } + +func (h *httpUserInternalAPI) SelectServerNoticeRoomID(ctx context.Context, req *api.QueryServerNoticeRoomRequest, res *api.QueryServerNoticeRoomResponse) (err error) { + span, ctx := opentracing.StartSpanFromContext(ctx, "SelectServerNoticeRoomID") + defer span.Finish() + + apiURL := h.apiURL + QueryServerNoticeRoomPath + return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) +} + +func (h *httpUserInternalAPI) UpdateServerNoticeRoomID(ctx context.Context, req *api.UpdateServerNoticeRoomRequest, res *api.UpdateServerNoticeRoomResponse) (err error) { + span, ctx := opentracing.StartSpanFromContext(ctx, "UpdateServerNoticeRoomID") + defer span.Finish() + + apiURL := h.apiURL + PerformUpdateServerNoticeRoomPath + return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) +} \ No newline at end of file diff --git a/userapi/inthttp/server.go b/userapi/inthttp/server.go index 66e2c6fd0f..c81dce19ab 100644 --- a/userapi/inthttp/server.go +++ b/userapi/inthttp/server.go @@ -389,4 +389,32 @@ func AddRoutes(internalAPIMux *mux.Router, s api.UserInternalAPI) { return util.JSONResponse{Code: http.StatusOK, JSON: &response} }), ) + internalAPIMux.Handle(QueryServerNoticeRoomPath, + httputil.MakeInternalAPI("queryServerNoticeRoom", func(req *http.Request) util.JSONResponse { + request := api.QueryServerNoticeRoomRequest{} + response := api.QueryServerNoticeRoomResponse{} + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + err := s.SelectServerNoticeRoomID(req.Context(), &request, &response) + if err != nil { + return util.JSONResponse{Code: http.StatusBadRequest, JSON: &response} + } + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) + internalAPIMux.Handle(PerformUpdateServerNoticeRoomPath, + httputil.MakeInternalAPI("performUpdateServerNoticeRoom", func(req *http.Request) util.JSONResponse { + request := api.UpdateServerNoticeRoomRequest{} + response := api.UpdateServerNoticeRoomResponse{} + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + err := s.UpdateServerNoticeRoomID(req.Context(), &request, &response) + if err != nil { + return util.JSONResponse{Code: http.StatusBadRequest, JSON: &response} + } + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) } diff --git a/userapi/storage/interface.go b/userapi/storage/interface.go index aca669820f..ad99a930f0 100644 --- a/userapi/storage/interface.go +++ b/userapi/storage/interface.go @@ -55,6 +55,8 @@ type Database interface { GetPrivacyPolicy(ctx context.Context, localpart string) (policyVersion string, err error) GetOutdatedPolicy(ctx context.Context, policyVersion string) (userIDs []string, err error) UpdatePolicyVersion(ctx context.Context, policyVersion, localpart string, serverNotice bool) error + SelectServerNoticeRoomID(ctx context.Context, localpart string) (roomID string, err error) + UpdateServerNoticeRoomID(ctx context.Context, localpart, roomID string) (err error) // Key backups CreateKeyBackup(ctx context.Context, userID, algorithm string, authData json.RawMessage) (version string, err error) diff --git a/userapi/storage/postgres/accounts_table.go b/userapi/storage/postgres/accounts_table.go index 22ef832746..097fe89986 100644 --- a/userapi/storage/postgres/accounts_table.go +++ b/userapi/storage/postgres/accounts_table.go @@ -47,7 +47,8 @@ CREATE TABLE IF NOT EXISTS account_accounts ( -- The policy version this user has accepted policy_version TEXT, -- The policy version the user received from the server notices room - policy_version_sent TEXT + policy_version_sent TEXT, + server_notice_room_id TEXT -- TODO: -- upgraded_ts, devices, any email reset stuff? ); @@ -85,6 +86,12 @@ const updatePolicyVersionSQL = "" + const updatePolicyVersionServerNoticeSQL = "" + "UPDATE account_accounts SET policy_version_sent = $1 WHERE localpart = $2" +const selectServerNoticeRoomSQL = "" + + "SELECT server_notice_room_id FROM account_accounts WHERE localpart = $1" + +const updateServerNoticeRoomSQL = "" + + "UPDATE account_accounts SET server_notice_room_id = $1 WHERE localpart = $2" + type accountsStatements struct { insertAccountStmt *sql.Stmt updatePasswordStmt *sql.Stmt @@ -96,6 +103,8 @@ type accountsStatements struct { batchSelectPrivacyPolicyStmt *sql.Stmt updatePolicyVersionStmt *sql.Stmt updatePolicyVersionServerNoticeStmt *sql.Stmt + selectServerNoticeRoomStmt *sql.Stmt + updateServerNoticeRoomStmt *sql.Stmt serverName gomatrixserverlib.ServerName } @@ -118,6 +127,8 @@ func NewPostgresAccountsTable(db *sql.DB, serverName gomatrixserverlib.ServerNam {&s.batchSelectPrivacyPolicyStmt, batchSelectPrivacyPolicySQL}, {&s.updatePolicyVersionStmt, updatePolicyVersionSQL}, {&s.updatePolicyVersionServerNoticeStmt, updatePolicyVersionServerNoticeSQL}, + {&s.selectServerNoticeRoomStmt, selectServerNoticeRoomSQL}, + {&s.updateServerNoticeRoomStmt, updateServerNoticeRoomSQL}, }.Prepare(db) } @@ -130,12 +141,7 @@ func (s *accountsStatements) InsertAccount( createdTimeMS := time.Now().UnixNano() / 1000000 stmt := sqlutil.TxStmt(txn, s.insertAccountStmt) - var err error - if accountType != api.AccountTypeAppService { - _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, nil, accountType, policyVersion) - } else { - _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID, accountType, "") - } + _, err := stmt.ExecContext(ctx, localpart, createdTimeMS, hash, nil, accountType, policyVersion) if err != nil { return nil, err } @@ -251,3 +257,36 @@ func (s *accountsStatements) UpdatePolicyVersion( _, err = stmt.ExecContext(ctx, policyVersion, localpart) return err } + +// SelectServerNoticeRoomID queries the server notice room ID. +func (s *accountsStatements) SelectServerNoticeRoomID( + ctx context.Context, txn *sql.Tx, localpart string, +) (roomID string, err error) { + stmt := s.selectServerNoticeRoomStmt + if txn != nil { + stmt = sqlutil.TxStmt(txn, stmt) + } + + roomIDNull := sql.NullString{} + row := stmt.QueryRowContext(ctx, localpart) + err = row.Scan(&roomIDNull) + if err != nil { + return "", err + } + if roomIDNull.Valid { + return roomIDNull.String, nil + } + return "", nil +} + +// UpdateServerNoticeRoomID sets the server notice room ID. +func (s *accountsStatements) UpdateServerNoticeRoomID( + ctx context.Context, txn *sql.Tx, localpart, roomID string, +) (err error) { + stmt := s.updateServerNoticeRoomStmt + if txn != nil { + stmt = sqlutil.TxStmt(txn, stmt) + } + _, err = stmt.ExecContext(ctx, roomID, localpart) + return +} \ No newline at end of file diff --git a/userapi/storage/postgres/deltas/2022021414375800_add_policy_version.go b/userapi/storage/postgres/deltas/2022021414375800_add_policy_version.go index 27347d21c3..1638fb4feb 100644 --- a/userapi/storage/postgres/deltas/2022021414375800_add_policy_version.go +++ b/userapi/storage/postgres/deltas/2022021414375800_add_policy_version.go @@ -20,13 +20,24 @@ func UpAddPolicyVersion(tx *sql.Tx) error { if err != nil { return fmt.Errorf("failed to execute upgrade: %w", err) } + _, err = tx.Exec("ALTER TABLE account_accounts ADD COLUMN IF NOT EXISTS server_notice_room_id TEXT;") + if err != nil { + return fmt.Errorf("failed to execute upgrade: %w", err) + } return nil } func DownAddPolicyVersion(tx *sql.Tx) error { - _, err := tx.Exec("ALTER TABLE account_accounts DROP COLUMN policy_version;" + - "ALTER TABLE account_accounts DROP COLUMN policy_version_sent;") + _, err := tx.Exec("ALTER TABLE account_accounts DROP COLUMN policy_version;") + if err != nil { + return fmt.Errorf("failed to execute downgrade: %w", err) + } + _, err = tx.Exec("ALTER TABLE account_accounts DROP COLUMN policy_version_sent;") + if err != nil { + return fmt.Errorf("failed to execute downgrade: %w", err) + } + _, err = tx.Exec("ALTER TABLE account_accounts DROP COLUMN server_notice_room_id;") if err != nil { return fmt.Errorf("failed to execute downgrade: %w", err) } diff --git a/userapi/storage/shared/storage.go b/userapi/storage/shared/storage.go index 262531dcba..605b90cd3d 100644 --- a/userapi/storage/shared/storage.go +++ b/userapi/storage/shared/storage.go @@ -791,3 +791,16 @@ func (d *Database) UpdatePolicyVersion(ctx context.Context, policyVersion, local }) return } + +// SelectServerNoticeRoomID returns the server notice room, if one is set. +func (d *Database) SelectServerNoticeRoomID(ctx context.Context, localpart string) (roomID string, err error) { + return d.Accounts.SelectServerNoticeRoomID(ctx, nil, localpart) +} + +// UpdateServerNoticeRoomID updates the server notice room +func (d *Database) UpdateServerNoticeRoomID(ctx context.Context, localpart, roomID string) (err error) { + err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { + return d.Accounts.UpdateServerNoticeRoomID(ctx, txn, localpart, roomID) + }) + return +} \ No newline at end of file diff --git a/userapi/storage/sqlite3/accounts_table.go b/userapi/storage/sqlite3/accounts_table.go index cbd073d514..9f7e75226f 100644 --- a/userapi/storage/sqlite3/accounts_table.go +++ b/userapi/storage/sqlite3/accounts_table.go @@ -47,7 +47,8 @@ CREATE TABLE IF NOT EXISTS account_accounts ( -- The policy version this user has accepted policy_version TEXT, -- The policy version the user received from the server notices room - policy_version_sent TEXT + policy_version_sent TEXT, + server_notice_room_id TEXT -- TODO: -- upgraded_ts, devices, any email reset stuff? ); @@ -83,6 +84,12 @@ const updatePolicyVersionSQL = "" + const updatePolicyVersionServerNoticeSQL = "" + "UPDATE account_accounts SET policy_version_sent = $1 WHERE localpart = $2" +const selectServerNoticeRoomSQL = "" + + "SELECT server_notice_room_id FROM account_accounts WHERE localpart = $1" + +const updateServerNoticeRoomSQL = "" + + "UPDATE account_accounts SET server_notice_room_id = $1 WHERE localpart = $2" + type accountsStatements struct { db *sql.DB insertAccountStmt *sql.Stmt @@ -95,6 +102,8 @@ type accountsStatements struct { batchSelectPrivacyPolicyStmt *sql.Stmt updatePolicyVersionStmt *sql.Stmt updatePolicyVersionServerNoticeStmt *sql.Stmt + selectServerNoticeRoomStmt *sql.Stmt + updateServerNoticeRoomStmt *sql.Stmt serverName gomatrixserverlib.ServerName } @@ -118,6 +127,8 @@ func NewSQLiteAccountsTable(db *sql.DB, serverName gomatrixserverlib.ServerName) {&s.batchSelectPrivacyPolicyStmt, batchSelectPrivacyPolicySQL}, {&s.updatePolicyVersionStmt, updatePolicyVersionSQL}, {&s.updatePolicyVersionServerNoticeStmt, updatePolicyVersionServerNoticeSQL}, + {&s.selectServerNoticeRoomStmt, selectServerNoticeRoomSQL}, + {&s.updateServerNoticeRoomStmt, updateServerNoticeRoomSQL}, }.Prepare(db) } @@ -130,12 +141,7 @@ func (s *accountsStatements) InsertAccount( createdTimeMS := time.Now().UnixNano() / 1000000 stmt := s.insertAccountStmt - var err error - if accountType != api.AccountTypeAppService { - _, err = sqlutil.TxStmt(txn, stmt).ExecContext(ctx, localpart, createdTimeMS, hash, nil, accountType, policyVersion) - } else { - _, err = sqlutil.TxStmt(txn, stmt).ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID, accountType, "") - } + _, err := sqlutil.TxStmt(txn, stmt).ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID, accountType, policyVersion) if err != nil { return nil, err } @@ -254,3 +260,36 @@ func (s *accountsStatements) UpdatePolicyVersion( _, err = stmt.ExecContext(ctx, policyVersion, localpart) return err } + +// SelectServerNoticeRoomID queries the server notice room ID. +func (s *accountsStatements) SelectServerNoticeRoomID( + ctx context.Context, txn *sql.Tx, localpart string, +) (roomID string, err error) { + stmt := s.selectServerNoticeRoomStmt + if txn != nil { + stmt = sqlutil.TxStmt(txn, stmt) + } + + roomIDNull := sql.NullString{} + row := stmt.QueryRowContext(ctx, localpart) + err = row.Scan(&roomIDNull) + if err != nil { + return "", err + } + if roomIDNull.Valid { + return roomIDNull.String, nil + } + return "", nil +} + +// UpdateServerNoticeRoomID sets the server notice room ID. +func (s *accountsStatements) UpdateServerNoticeRoomID( + ctx context.Context, txn *sql.Tx, localpart, roomID string, +) (err error) { + stmt := s.updateServerNoticeRoomStmt + if txn != nil { + stmt = sqlutil.TxStmt(txn, stmt) + } + _, err = stmt.ExecContext(ctx, roomID, localpart) + return +} \ No newline at end of file diff --git a/userapi/storage/sqlite3/deltas/2022021414375800_add_policy_version.go b/userapi/storage/sqlite3/deltas/2022021414375800_add_policy_version.go index 683210ca74..251ec4e40b 100644 --- a/userapi/storage/sqlite3/deltas/2022021414375800_add_policy_version.go +++ b/userapi/storage/sqlite3/deltas/2022021414375800_add_policy_version.go @@ -20,12 +20,23 @@ func UpAddPolicyVersion(tx *sql.Tx) error { if err != nil { return fmt.Errorf("failed to execute upgrade: %w", err) } + _, err = tx.Exec("ALTER TABLE account_accounts ADD COLUMN server_notice_room_id TEXT;") + if err != nil { + return fmt.Errorf("failed to execute upgrade: %w", err) + } return nil } func DownAddPolicyVersion(tx *sql.Tx) error { - _, err := tx.Exec("ALTER TABLE account_accounts DROP COLUMN policy_version;" + - "ALTER TABLE account_accounts DROP COLUMN policy_version_sent;") + _, err := tx.Exec("ALTER TABLE account_accounts DROP COLUMN policy_version;") + if err != nil { + return fmt.Errorf("failed to execute downgrade: %w", err) + } + _, err = tx.Exec("ALTER TABLE account_accounts DROP COLUMN policy_version_sent;") + if err != nil { + return fmt.Errorf("failed to execute downgrade: %w", err) + } + _, err = tx.Exec("ALTER TABLE account_accounts DROP COLUMN server_notice_room_id;") if err != nil { return fmt.Errorf("failed to execute downgrade: %w", err) } diff --git a/userapi/storage/tables/interface.go b/userapi/storage/tables/interface.go index ef067ed076..caf41a91c9 100644 --- a/userapi/storage/tables/interface.go +++ b/userapi/storage/tables/interface.go @@ -41,6 +41,8 @@ type AccountsTable interface { SelectPrivacyPolicy(ctx context.Context, txn *sql.Tx, localPart string) (policy string, err error) BatchSelectPrivacyPolicy(ctx context.Context, txn *sql.Tx, policyVersion string) (userIDs []string, err error) UpdatePolicyVersion(ctx context.Context, txn *sql.Tx, policyVersion, localpart string, serverNotice bool) (err error) + SelectServerNoticeRoomID(ctx context.Context, txn *sql.Tx, localpart string) (roomID string, err error) + UpdateServerNoticeRoomID(ctx context.Context, txn *sql.Tx, localpart, roomID string) (err error) } type DevicesTable interface { From 7c6a162c0f5dd667b4a7cb9a3e031acf12d6ca76 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 7 Mar 2022 09:42:02 +0100 Subject: [PATCH 38/56] Remove MemberShipStateAll --- roomserver/storage/postgres/membership_table.go | 10 ++-------- roomserver/storage/shared/storage.go | 2 -- roomserver/storage/sqlite3/membership_table.go | 9 ++------- roomserver/storage/tables/interface.go | 1 - 4 files changed, 4 insertions(+), 18 deletions(-) diff --git a/roomserver/storage/postgres/membership_table.go b/roomserver/storage/postgres/membership_table.go index 33a965457b..22d0643713 100644 --- a/roomserver/storage/postgres/membership_table.go +++ b/roomserver/storage/postgres/membership_table.go @@ -296,14 +296,8 @@ func (s *membershipStatements) SelectRoomsWithMembership( rows *sql.Rows err error ) - - if membershipState == tables.MemberShipStateAll { - stmt := sqlutil.TxStmt(txn, s.selectRoomsForUserStmt) - rows, err = stmt.QueryContext(ctx, userID) - } else { - stmt := sqlutil.TxStmt(txn, s.selectRoomsWithMembershipStmt) - rows, err = stmt.QueryContext(ctx, membershipState, userID) - } + stmt := sqlutil.TxStmt(txn, s.selectRoomsWithMembershipStmt) + rows, err = stmt.QueryContext(ctx, membershipState, userID) if err != nil { return nil, err diff --git a/roomserver/storage/shared/storage.go b/roomserver/storage/shared/storage.go index eae8cd2cba..dd49f35ca8 100644 --- a/roomserver/storage/shared/storage.go +++ b/roomserver/storage/shared/storage.go @@ -1046,8 +1046,6 @@ func (d *Database) GetRoomsByMembership(ctx context.Context, userID, membership membershipState = tables.MembershipStateLeaveOrBan case "ban": membershipState = tables.MembershipStateLeaveOrBan - case "all": - membershipState = tables.MemberShipStateAll default: return nil, fmt.Errorf("GetRoomsByMembership: invalid membership %s", membership) } diff --git a/roomserver/storage/sqlite3/membership_table.go b/roomserver/storage/sqlite3/membership_table.go index ff291a3a70..613496882e 100644 --- a/roomserver/storage/sqlite3/membership_table.go +++ b/roomserver/storage/sqlite3/membership_table.go @@ -274,13 +274,8 @@ func (s *membershipStatements) SelectRoomsWithMembership( err error ) - if membershipState == tables.MemberShipStateAll { - stmt := sqlutil.TxStmt(txn, s.selectRoomsForUserStmt) - rows, err = stmt.QueryContext(ctx, userID) - } else { - stmt := sqlutil.TxStmt(txn, s.selectRoomsWithMembershipStmt) - rows, err = stmt.QueryContext(ctx, membershipState, userID) - } + stmt := sqlutil.TxStmt(txn, s.selectRoomsWithMembershipStmt) + rows, err = stmt.QueryContext(ctx, membershipState, userID) if err != nil { return nil, err diff --git a/roomserver/storage/tables/interface.go b/roomserver/storage/tables/interface.go index e814fb0f37..04e3c96cca 100644 --- a/roomserver/storage/tables/interface.go +++ b/roomserver/storage/tables/interface.go @@ -113,7 +113,6 @@ type Invites interface { type MembershipState int64 const ( - MemberShipStateAll MembershipState = 0 MembershipStateLeaveOrBan MembershipState = 1 MembershipStateInvite MembershipState = 2 MembershipStateJoin MembershipState = 3 From c7d2254698eab990091417db73c5a23e6d12041c Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 7 Mar 2022 09:45:24 +0100 Subject: [PATCH 39/56] Update templates, remove default base URL --- dendrite-config.yaml | 2 +- docs/templates/privacy/1.0.gohtml | 6 ++--- internal/httputil/httpapi.go | 19 ++++++++------ mediaapi/routing/routing.go | 4 +-- setup/config/config_global.go | 2 +- setup/config/config_global_test.go | 7 +++++- setup/config/testdata/privacy/1.0.gohtml | 32 ++++++++++++------------ setup/mscs/msc2836/msc2836.go | 2 +- 8 files changed, 41 insertions(+), 33 deletions(-) diff --git a/dendrite-config.yaml b/dendrite-config.yaml index c537bd1ef9..b137ff4786 100644 --- a/dendrite-config.yaml +++ b/dendrite-config.yaml @@ -86,7 +86,7 @@ global: enabled: false # The base URL this homeserver will serve clients on, e.g. https://matrix.org base_url: http://localhost - # Randomly generated string to be used to calculate the HMAC + # Randomly generated string (e.g. by using "pwgen -sy 32") to be used to calculate the HMAC form_secret: "superSecretRandomlyGeneratedSecret" # Require consent when user registers for the first time require_at_registration: false diff --git a/docs/templates/privacy/1.0.gohtml b/docs/templates/privacy/1.0.gohtml index 8704ff2e2f..6e866cff2e 100644 --- a/docs/templates/privacy/1.0.gohtml +++ b/docs/templates/privacy/1.0.gohtml @@ -12,13 +12,13 @@

Please give your consent to keep using this homeserver.

- {{ if not .PublicVersion }} + {{ if not .ReadOnly }}
- + - +
{{ end }} {{ end }} diff --git a/internal/httputil/httpapi.go b/internal/httputil/httpapi.go index 910d928766..ba8f67cf4d 100644 --- a/internal/httputil/httpapi.go +++ b/internal/httputil/httpapi.go @@ -25,6 +25,7 @@ import ( "net/http" "net/http/httptest" "net/http/httputil" + "net/url" "os" "strings" "sync" @@ -119,14 +120,14 @@ func MakeAuthAPI( } func checkConsent(ctx context.Context, userID string, userAPI userapi.UserInternalAPI, userConsentCfg config.UserConsentOptions) *util.JSONResponse { - localPart, _, err := gomatrixserverlib.SplitID('@', userID) + localpart, _, err := gomatrixserverlib.SplitID('@', userID) if err != nil { return nil } // check which version of the policy the user accepted res := &userapi.QueryPolicyVersionResponse{} err = userAPI.QueryPolicyVersion(ctx, &userapi.QueryPolicyVersionRequest{ - Localpart: localPart, + Localpart: localpart, }, res) if err != nil { return &util.JSONResponse{ @@ -166,18 +167,20 @@ func checkConsent(ctx context.Context, userID string, userAPI userapi.UserIntern } // getConsentURL constructs the URL shown to users to accept the TOS -func getConsentURL(username string, config config.UserConsentOptions) (string, error) { +func getConsentURL(userID string, config config.UserConsentOptions) (string, error) { mac := hmac.New(sha256.New, []byte(config.FormSecret)) - _, err := mac.Write([]byte(username)) + _, err := mac.Write([]byte(userID)) if err != nil { return "", err } hmac := hex.EncodeToString(mac.Sum(nil)) - return fmt.Sprintf( - "%s/_matrix/client/consent?u=%s&h=%s&v=%s", - config.BaseURL, username, hmac, config.Version, - ), nil + params := url.Values{} + params.Add("u", userID) + params.Add("h", string(hmac)) + params.Add("v", config.Version) + + return fmt.Sprintf("%s/_matrix/client/consent?%s", config.BaseURL, params.Encode()), nil } // MakeExternalAPI turns a util.JSONRequestHandler function into an http.Handler. diff --git a/mediaapi/routing/routing.go b/mediaapi/routing/routing.go index 4e37a8bb5b..ff4d315013 100644 --- a/mediaapi/routing/routing.go +++ b/mediaapi/routing/routing.go @@ -59,14 +59,14 @@ func Setup( PathToResult: map[string]*types.ThumbnailGenerationResult{}, } - uploadHandler := httputil.MakeAuthAPI("upload", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, dev *userapi.Device) util.JSONResponse { + uploadHandler := httputil.MakeAuthAPI("upload", userAPI, func(req *http.Request, dev *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } return Upload(req, cfg, dev, db, activeThumbnailGeneration) }) - configHandler := httputil.MakeAuthAPI("config", userAPI, cfg.Matrix.UserConsentOptions, false, func(req *http.Request, device *userapi.Device) util.JSONResponse { + configHandler := httputil.MakeAuthAPI("config", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.Limit(req); r != nil { return *r } diff --git a/setup/config/config_global.go b/setup/config/config_global.go index 19eec8be45..098d278a8b 100644 --- a/setup/config/config_global.go +++ b/setup/config/config_global.go @@ -275,7 +275,6 @@ func (c *UserConsentOptions) Defaults() { c.PolicyName = "Privacy Policy" c.Version = "1.0" c.TemplateDir = "./templates/privacy" - c.BaseURL = "http://localhost" } func (c *UserConsentOptions) Verify(configErrors *ConfigErrors, isMonolith bool) { @@ -284,6 +283,7 @@ func (c *UserConsentOptions) Verify(configErrors *ConfigErrors, isMonolith bool) checkNotEmpty(configErrors, "version", c.Version) checkNotEmpty(configErrors, "policy_name", c.PolicyName) checkNotEmpty(configErrors, "form_secret", c.FormSecret) + checkNotEmpty(configErrors, "base_url", c.BaseURL) if len(*configErrors) > 0 { return } diff --git a/setup/config/config_global_test.go b/setup/config/config_global_test.go index a3ede2d59a..ab61514ade 100644 --- a/setup/config/config_global_test.go +++ b/setup/config/config_global_test.go @@ -82,6 +82,8 @@ func TestUserConsentOptions_Verify(t *testing.T) { TemplateDir: "./testdata/privacy", Version: "1.0", PolicyName: "Privacy policy", + FormSecret: "helloWorld", + BaseURL: "http://localhost", }, args: struct { configErrors *ConfigErrors @@ -93,6 +95,9 @@ func TestUserConsentOptions_Verify(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &UserConsentOptions{ + Enabled: true, + BaseURL: tt.fields.BaseURL, + FormSecret: tt.fields.FormSecret, RequireAtRegistration: tt.fields.RequireAtRegistration, PolicyName: tt.fields.PolicyName, Version: tt.fields.Version, @@ -102,7 +107,7 @@ func TestUserConsentOptions_Verify(t *testing.T) { BlockEventsError: tt.fields.BlockEventsError, } c.Verify(tt.args.configErrors, tt.args.isMonolith) - if tt.wantErr && tt.args.configErrors == nil { + if !tt.wantErr && len(*tt.args.configErrors) > 0 { t.Errorf("expected no errors, got '%+v'", tt.args.configErrors) } }) diff --git a/setup/config/testdata/privacy/1.0.gohtml b/setup/config/testdata/privacy/1.0.gohtml index 12602fada9..6e866cff2e 100644 --- a/setup/config/testdata/privacy/1.0.gohtml +++ b/setup/config/testdata/privacy/1.0.gohtml @@ -1,26 +1,26 @@ - Matrix.org Privacy policy + Privacy policy {{ if .HasConsented }} -

- Your base already belong to us. -

+

+ You have already given your consent. +

{{ else }} -

- All your base are belong to us. -

-{{ if not .PublicVersion }} - -
- - - - -
-{{ end }} +

+ Please give your consent to keep using this homeserver. +

+ {{ if not .ReadOnly }} + +
+ + + + +
+ {{ end }} {{ end }} \ No newline at end of file diff --git a/setup/mscs/msc2836/msc2836.go b/setup/mscs/msc2836/msc2836.go index 7678dcb863..29c781a889 100644 --- a/setup/mscs/msc2836/msc2836.go +++ b/setup/mscs/msc2836/msc2836.go @@ -126,7 +126,7 @@ func Enable( }) base.PublicClientAPIMux.Handle("/unstable/event_relationships", - httputil.MakeAuthAPI("eventRelationships", userAPI, base.Cfg.Global.UserConsentOptions, false, eventRelationshipHandler(db, rsAPI, fsAPI)), + httputil.MakeAuthAPI("eventRelationships", userAPI, eventRelationshipHandler(db, rsAPI, fsAPI)), ).Methods(http.MethodPost, http.MethodOptions) base.PublicFederationAPIMux.Handle("/unstable/event_relationships", httputil.MakeExternalAPI( From dcfc0bcd4317e19e4a3bc0196b040e6f095494dc Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 7 Mar 2022 09:45:59 +0100 Subject: [PATCH 40/56] URL Encode, use new method to get server notice room --- clientapi/routing/consent_tracking.go | 8 ++- clientapi/routing/server_notices.go | 74 ++++++++++----------------- 2 files changed, 35 insertions(+), 47 deletions(-) diff --git a/clientapi/routing/consent_tracking.go b/clientapi/routing/consent_tracking.go index 2ba5e6215a..92a31a41af 100644 --- a/clientapi/routing/consent_tracking.go +++ b/clientapi/routing/consent_tracking.go @@ -22,6 +22,7 @@ import ( "encoding/hex" "fmt" "net/http" + "net/url" appserviceAPI "github.com/matrix-org/dendrite/appservice/api" "github.com/matrix-org/dendrite/clientapi/jsonerror" @@ -210,7 +211,12 @@ func buildConsentURI(cfgClient *config.ClientAPI, userID string) (string, error) } userMAC := mac.Sum(nil) - return fmt.Sprintf("%s/_matrix/client/consent?u=%s&h=%s&v=%s", cfgClient.Matrix.UserConsentOptions.BaseURL, userID, userMAC, consentOpts.Version), nil + params := url.Values{} + params.Add("u", userID) + params.Add("h", string(userMAC)) + params.Add("v", consentOpts.Version) + + return fmt.Sprintf("%s/_matrix/client/consent?%s", cfgClient.Matrix.UserConsentOptions.BaseURL, params.Encode()), nil } func validHMAC(username, userHMAC, secret string) (bool, error) { diff --git a/clientapi/routing/server_notices.go b/clientapi/routing/server_notices.go index 59c2db4e08..fcc670ae82 100644 --- a/clientapi/routing/server_notices.go +++ b/clientapi/routing/server_notices.go @@ -112,43 +112,25 @@ func sendServerNotice( }, nil } - // get rooms for specified user - allUserRooms, err := getAllUserRooms(ctx, rsAPI, serverNoticeRequest.UserID) + qryServerNoticeRoom := &userapi.QueryServerNoticeRoomResponse{} + localpart, _, err := gomatrixserverlib.SplitID('@', serverNoticeRequest.UserID) if err != nil { - return util.ErrorResponse(err), nil + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.BadJSON("Invalid request"), + }, nil } - - // get rooms of the sender - senderUserID := fmt.Sprintf("@%s:%s", cfgNotices.LocalPart, cfgClient.Matrix.ServerName) - senderRooms := api.QueryRoomsForUserResponse{} - if err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{ - UserID: senderUserID, - WantMembership: "join", - }, &senderRooms); err != nil { + err = userAPI.SelectServerNoticeRoomID(ctx, &userapi.QueryServerNoticeRoomRequest{Localpart: localpart}, qryServerNoticeRoom) + if err != nil { return util.ErrorResponse(err), nil } - // check if we have rooms in common - commonRooms := []string{} - for _, userRoomID := range allUserRooms { - for _, senderRoomID := range senderRooms.RoomIDs { - if userRoomID == senderRoomID { - commonRooms = append(commonRooms, senderRoomID) - } - } - } - - if len(commonRooms) > 1 { - return util.ErrorResponse(fmt.Errorf("expected to find one room, but got %d", len(commonRooms))), nil - } - - var ( - roomID string - roomVersion = gomatrixserverlib.RoomVersionV6 - ) + senderUserID := fmt.Sprintf("@%s:%s", cfgNotices.LocalPart, cfgClient.Matrix.ServerName) + roomID := qryServerNoticeRoom.RoomID + roomVersion := gomatrixserverlib.RoomVersionV6 // create a new room for the user - if len(commonRooms) == 0 { + if qryServerNoticeRoom.RoomID == "" { powerLevelContent := eventutil.InitialPowerLevelsContent(senderUserID) powerLevelContent.Users[serverNoticeRequest.UserID] = -10 // taken from Synapse pl, err := json.Marshal(powerLevelContent) @@ -177,7 +159,12 @@ func sendServerNotice( switch data := roomRes.JSON.(type) { case createRoomResponse: roomID = data.RoomID - + res := &userapi.UpdateServerNoticeRoomResponse{} + err := userAPI.UpdateServerNoticeRoomID(ctx, &userapi.UpdateServerNoticeRoomRequest{RoomID: roomID, Localpart: localpart}, res) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("UpdateServerNoticeRoomID failed") + return jsonerror.InternalServerError(), nil + } // tag the room, so we can later check if the user tries to reject an invite serverAlertTag := gomatrix.TagContent{Tags: map[string]gomatrix.TagProperties{ "m.server_notice": { @@ -195,11 +182,17 @@ func sendServerNotice( } } else { - roomID = commonRooms[0] - // re-invite the user - res, err := sendInvite(ctx, accountsDB, senderDevice, roomID, serverNoticeRequest.UserID, "Server notice room", cfgClient, rsAPI, asAPI, time.Now()) + res := &api.QueryMembershipForUserResponse{} + err := rsAPI.QueryMembershipForUser(ctx, &api.QueryMembershipForUserRequest{UserID: serverNoticeRequest.UserID, RoomID: roomID}, res) if err != nil { - return res, nil + return util.ErrorResponse(err), nil + } + // re-invite the user + if res.Membership != gomatrixserverlib.Join { + res, err := sendInvite(ctx, accountsDB, senderDevice, roomID, serverNoticeRequest.UserID, "Server notice room", cfgClient, rsAPI, asAPI, time.Now()) + if err != nil { + return res, nil + } } } @@ -265,17 +258,6 @@ func sendServerNotice( return res, nil } -func getAllUserRooms(ctx context.Context, rsAPI api.RoomserverInternalAPI, userID string) ([]string, error) { - userRooms := api.QueryRoomsForUserResponse{} - if err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{ - UserID: userID, - WantMembership: "all", - }, &userRooms); err != nil { - return nil, err - } - return userRooms.RoomIDs, nil -} - func (r sendServerNoticeRequest) valid() (ok bool) { if r.UserID == "" { return false From 31ac3ac081ed952c98467c64c5dbc92722c0c7b2 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Wed, 16 Mar 2022 08:42:04 +0100 Subject: [PATCH 41/56] Use DefaultRoomVersion as roomVersion --- clientapi/routing/server_notices.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clientapi/routing/server_notices.go b/clientapi/routing/server_notices.go index fcc670ae82..224eff3a1d 100644 --- a/clientapi/routing/server_notices.go +++ b/clientapi/routing/server_notices.go @@ -21,6 +21,7 @@ import ( "net/http" "time" + "github.com/matrix-org/dendrite/roomserver/version" userdb "github.com/matrix-org/dendrite/userapi/storage" "github.com/matrix-org/gomatrix" "github.com/matrix-org/gomatrixserverlib" @@ -127,7 +128,7 @@ func sendServerNotice( senderUserID := fmt.Sprintf("@%s:%s", cfgNotices.LocalPart, cfgClient.Matrix.ServerName) roomID := qryServerNoticeRoom.RoomID - roomVersion := gomatrixserverlib.RoomVersionV6 + roomVersion := version.DefaultRoomVersion() // create a new room for the user if qryServerNoticeRoom.RoomID == "" { From f1e8d19cea3c28116c3d2519cedfbd43d56efc3c Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 28 Mar 2022 11:33:12 +0200 Subject: [PATCH 42/56] Fix build --- clientapi/routing/consent_tracking.go | 4 +--- clientapi/routing/routing.go | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/clientapi/routing/consent_tracking.go b/clientapi/routing/consent_tracking.go index 92a31a41af..f61c8d9972 100644 --- a/clientapi/routing/consent_tracking.go +++ b/clientapi/routing/consent_tracking.go @@ -29,7 +29,6 @@ import ( "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/setup/config" userapi "github.com/matrix-org/dendrite/userapi/api" - userdb "github.com/matrix-org/dendrite/userapi/storage" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" "github.com/sirupsen/logrus" @@ -129,7 +128,6 @@ func sendServerNoticeForConsent(userAPI userapi.UserInternalAPI, rsAPI api.Rooms cfgNotices *config.ServerNotices, cfgClient *config.ClientAPI, senderDevice *userapi.Device, - accountsDB userdb.Database, asAPI appserviceAPI.AppServiceQueryAPI, ) { res := &userapi.QueryOutdatedPolicyResponse{} @@ -180,7 +178,7 @@ func sendServerNoticeForConsent(userAPI userapi.UserInternalAPI, rsAPI api.Rooms Body: msgBody.String(), }, } - _, err = sendServerNotice(context.Background(), req, rsAPI, cfgNotices, cfgClient, senderDevice, accountsDB, asAPI, userAPI, nil, nil, nil) + _, err = sendServerNotice(context.Background(), req, rsAPI, cfgNotices, cfgClient, senderDevice, asAPI, userAPI, nil, nil, nil) if err != nil { logrus.WithError(err).WithField("userID", userID).Error("failed to send server notice for consent to user") continue diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index ddf5400699..bac4379a7c 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -178,7 +178,7 @@ func Setup( logrus.Warnf("Consent tracking is enabled, but server notes are not. No server notice will be sent to users") } else { // start a new go routine to send messages about consent - go sendServerNoticeForConsent(userAPI, rsAPI, &cfg.Matrix.ServerNotices, cfg, serverNotificationSender, accountDB, asAPI) + go sendServerNoticeForConsent(userAPI, rsAPI, &cfg.Matrix.ServerNotices, cfg, serverNotificationSender, asAPI) } publicAPIMux.Handle("/consent", httputil.MakeHTMLAPI("consent", func(writer http.ResponseWriter, request *http.Request) *util.JSONResponse { From c99e3aff1b79270aa5c9a9c1b26e4bfaaa1dde0b Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 28 Mar 2022 12:01:15 +0200 Subject: [PATCH 43/56] Fix linter issues --- clientapi/routing/consent_tracking.go | 2 +- clientapi/routing/server_notices.go | 36 ++++++++++++---------- userapi/storage/postgres/accounts_table.go | 5 +-- userapi/storage/sqlite3/accounts_table.go | 5 +-- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/clientapi/routing/consent_tracking.go b/clientapi/routing/consent_tracking.go index f61c8d9972..c3075e15b2 100644 --- a/clientapi/routing/consent_tracking.go +++ b/clientapi/routing/consent_tracking.go @@ -96,7 +96,7 @@ func consent(writer http.ResponseWriter, req *http.Request, userAPI userapi.User } return &internalError } - if err := userAPI.PerformUpdatePolicyVersion( + if err = userAPI.PerformUpdatePolicyVersion( req.Context(), &userapi.UpdatePolicyVersionRequest{ PolicyVersion: data.Version, diff --git a/clientapi/routing/server_notices.go b/clientapi/routing/server_notices.go index effef57744..e865392738 100644 --- a/clientapi/routing/server_notices.go +++ b/clientapi/routing/server_notices.go @@ -107,7 +107,7 @@ func sendServerNotice( return util.JSONResponse{ Code: http.StatusBadRequest, JSON: jsonerror.BadJSON("Invalid request"), - }, nil + }, fmt.Errorf("Invalid JSON") } qryServerNoticeRoom := &userapi.QueryServerNoticeRoomResponse{} @@ -116,11 +116,11 @@ func sendServerNotice( return util.JSONResponse{ Code: http.StatusBadRequest, JSON: jsonerror.BadJSON("Invalid request"), - }, nil + }, fmt.Errorf("Invalid JSON") } err = userAPI.SelectServerNoticeRoomID(ctx, &userapi.QueryServerNoticeRoomRequest{Localpart: localpart}, qryServerNoticeRoom) if err != nil { - return util.ErrorResponse(err), nil + return util.ErrorResponse(err), err } senderUserID := fmt.Sprintf("@%s:%s", cfgNotices.LocalPart, cfgClient.Matrix.ServerName) @@ -129,17 +129,18 @@ func sendServerNotice( // create a new room for the user if qryServerNoticeRoom.RoomID == "" { + var pl, cc []byte powerLevelContent := eventutil.InitialPowerLevelsContent(senderUserID) powerLevelContent.Users[serverNoticeRequest.UserID] = -10 // taken from Synapse - pl, err := json.Marshal(powerLevelContent) + pl, err = json.Marshal(powerLevelContent) if err != nil { - return util.ErrorResponse(err), nil + return util.ErrorResponse(err), err } createContent := map[string]interface{}{} createContent["m.federate"] = false - cc, err := json.Marshal(createContent) + cc, err = json.Marshal(createContent) if err != nil { - return util.ErrorResponse(err), nil + return util.ErrorResponse(err), err } crReq := createRoomRequest{ Invite: []string{serverNoticeRequest.UserID}, @@ -158,7 +159,7 @@ func sendServerNotice( case createRoomResponse: roomID = data.RoomID res := &userapi.UpdateServerNoticeRoomResponse{} - err := userAPI.UpdateServerNoticeRoomID(ctx, &userapi.UpdateServerNoticeRoomRequest{RoomID: roomID, Localpart: localpart}, res) + err = userAPI.UpdateServerNoticeRoomID(ctx, &userapi.UpdateServerNoticeRoomRequest{RoomID: roomID, Localpart: localpart}, res) if err != nil { util.GetLogger(ctx).WithError(err).Error("UpdateServerNoticeRoomID failed") return jsonerror.InternalServerError(), nil @@ -171,25 +172,26 @@ func sendServerNotice( }} if err = saveTagData(ctx, serverNoticeRequest.UserID, roomID, userAPI, serverAlertTag); err != nil { util.GetLogger(ctx).WithError(err).Error("saveTagData failed") - return jsonerror.InternalServerError(), nil + return jsonerror.InternalServerError(), err } default: // if we didn't get a createRoomResponse, we probably received an error, so return that. - return roomRes, nil + return roomRes, fmt.Errorf("Unable to create room") } } else { res := &api.QueryMembershipForUserResponse{} - err := rsAPI.QueryMembershipForUser(ctx, &api.QueryMembershipForUserRequest{UserID: serverNoticeRequest.UserID, RoomID: roomID}, res) + err = rsAPI.QueryMembershipForUser(ctx, &api.QueryMembershipForUserRequest{UserID: serverNoticeRequest.UserID, RoomID: roomID}, res) if err != nil { - return util.ErrorResponse(err), nil + return util.ErrorResponse(err), err } // re-invite the user if res.Membership != gomatrixserverlib.Join { - res, err := sendInvite(ctx, userAPI, senderDevice, roomID, serverNoticeRequest.UserID, "Server notice room", cfgClient, rsAPI, asAPI, time.Now()) + var inviteRes util.JSONResponse + inviteRes, err = sendInvite(ctx, userAPI, senderDevice, roomID, serverNoticeRequest.UserID, "Server notice room", cfgClient, rsAPI, asAPI, time.Now()) if err != nil { - return res, nil + return inviteRes, err } } } @@ -203,7 +205,7 @@ func sendServerNotice( e, resErr := generateSendEvent(ctx, request, senderDevice, roomID, "m.room.message", nil, cfgClient, rsAPI, time.Now()) if resErr != nil { logrus.Errorf("failed to send message: %+v", resErr) - return *resErr, nil + return *resErr, fmt.Errorf("Unable to send event") } timeToGenerateEvent := time.Since(startedGeneratingEvent) @@ -218,7 +220,7 @@ func sendServerNotice( // pass the new event to the roomserver and receive the correct event ID // event ID in case of duplicate transaction is discarded startedSubmittingEvent := time.Now() - if err := api.SendEvents( + if err = api.SendEvents( ctx, rsAPI, api.KindNew, []*gomatrixserverlib.HeaderedEvent{ @@ -230,7 +232,7 @@ func sendServerNotice( false, ); err != nil { util.GetLogger(ctx).WithError(err).Error("SendEvents failed") - return jsonerror.InternalServerError(), nil + return jsonerror.InternalServerError(), err } util.GetLogger(ctx).WithFields(logrus.Fields{ "event_id": e.EventID(), diff --git a/userapi/storage/postgres/accounts_table.go b/userapi/storage/postgres/accounts_table.go index 097fe89986..c7ae8f1033 100644 --- a/userapi/storage/postgres/accounts_table.go +++ b/userapi/storage/postgres/accounts_table.go @@ -19,6 +19,7 @@ import ( "database/sql" "time" + "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/dendrite/clientapi/userutil" @@ -232,7 +233,7 @@ func (s *accountsStatements) BatchSelectPrivacyPolicy( stmt = sqlutil.TxStmt(txn, stmt) } rows, err := stmt.QueryContext(ctx, policyVersion) - defer rows.Close() + defer internal.CloseAndLogIfError(ctx, rows, "BatchSelectPrivacyPolicy: rows.close() failed") for rows.Next() { var userID string if err := rows.Scan(&userID); err != nil { @@ -289,4 +290,4 @@ func (s *accountsStatements) UpdateServerNoticeRoomID( } _, err = stmt.ExecContext(ctx, roomID, localpart) return -} \ No newline at end of file +} diff --git a/userapi/storage/sqlite3/accounts_table.go b/userapi/storage/sqlite3/accounts_table.go index 9f7e75226f..5fb6b5ffef 100644 --- a/userapi/storage/sqlite3/accounts_table.go +++ b/userapi/storage/sqlite3/accounts_table.go @@ -19,6 +19,7 @@ import ( "database/sql" "time" + "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/dendrite/clientapi/userutil" @@ -235,7 +236,7 @@ func (s *accountsStatements) BatchSelectPrivacyPolicy( if err != nil { return nil, err } - defer rows.Close() + defer internal.CloseAndLogIfError(ctx, rows, "BatchSelectPrivacyPolicy: rows.close() failed") for rows.Next() { var userID string if err := rows.Scan(&userID); err != nil { @@ -292,4 +293,4 @@ func (s *accountsStatements) UpdateServerNoticeRoomID( } _, err = stmt.ExecContext(ctx, roomID, localpart) return -} \ No newline at end of file +} From 2a18023a1a16a3533302c0af1da7a1a3e243465a Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 28 Mar 2022 12:13:47 +0200 Subject: [PATCH 44/56] Linter again.. --- clientapi/clientapi.go | 1 - userapi/internal/api.go | 2 +- userapi/storage/shared/storage.go | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/clientapi/clientapi.go b/clientapi/clientapi.go index 5bcab8a006..31a53a7061 100644 --- a/clientapi/clientapi.go +++ b/clientapi/clientapi.go @@ -63,4 +63,3 @@ func AddPublicRoutes( extRoomsProvider, mscCfg, ) } - diff --git a/userapi/internal/api.go b/userapi/internal/api.go index f059548340..2763df1366 100644 --- a/userapi/internal/api.go +++ b/userapi/internal/api.go @@ -886,4 +886,4 @@ func (a *UserInternalAPI) UpdateServerNoticeRoomID( res *api.UpdateServerNoticeRoomResponse, ) (err error) { return a.DB.UpdateServerNoticeRoomID(ctx, req.Localpart, req.RoomID) -} \ No newline at end of file +} diff --git a/userapi/storage/shared/storage.go b/userapi/storage/shared/storage.go index e69aaf55ec..eadf6f8165 100644 --- a/userapi/storage/shared/storage.go +++ b/userapi/storage/shared/storage.go @@ -809,4 +809,4 @@ func (d *Database) UpdateServerNoticeRoomID(ctx context.Context, localpart, room return d.Accounts.UpdateServerNoticeRoomID(ctx, txn, localpart, roomID) }) return -} \ No newline at end of file +} From dc8cea6d575ff3510d7ed293207390f06700dfba Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Wed, 4 May 2022 13:47:08 +0200 Subject: [PATCH 45/56] PR comments config --- setup/config/config_global.go | 64 ++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/setup/config/config_global.go b/setup/config/config_global.go index 10f2779693..25ea91d436 100644 --- a/setup/config/config_global.go +++ b/setup/config/config_global.go @@ -288,37 +288,39 @@ func (c *UserConsentOptions) Defaults() { } func (c *UserConsentOptions) Verify(configErrors *ConfigErrors, isMonolith bool) { - if c.Enabled { - checkNotEmpty(configErrors, "template_dir", c.TemplateDir) - checkNotEmpty(configErrors, "version", c.Version) - checkNotEmpty(configErrors, "policy_name", c.PolicyName) - checkNotEmpty(configErrors, "form_secret", c.FormSecret) - checkNotEmpty(configErrors, "base_url", c.BaseURL) - if len(*configErrors) > 0 { - return - } - - p, err := filepath.Abs(c.TemplateDir) - if err != nil { - configErrors.Add("unable to get template directory") - return - } - - c.TextTemplates = textTemplate.Must(textTemplate.New("blockEventsError").Parse(c.BlockEventsError)) - c.TextTemplates = textTemplate.Must(c.TextTemplates.New("serverNoticeTemplate").Parse(c.ServerNoticeContent.Body)) - - // Read all defined *.gohtml templates - t, err := template.ParseGlob(filepath.Join(p, "*.gohtml")) - if err != nil || t == nil { - configErrors.Add(fmt.Sprintf("unable to read consent templates: %+v", err)) - return - } - c.Templates = t - // Verify we've got a template for the defined version - versionTemplate := c.Templates.Lookup(c.Version + ".gohtml") - if versionTemplate == nil { - configErrors.Add(fmt.Sprintf("unable to load defined '%s' policy template", c.Version)) - } + if !c.Enabled { + return + } + + checkNotEmpty(configErrors, "template_dir", c.TemplateDir) + checkNotEmpty(configErrors, "version", c.Version) + checkNotEmpty(configErrors, "policy_name", c.PolicyName) + checkNotEmpty(configErrors, "form_secret", c.FormSecret) + checkNotEmpty(configErrors, "base_url", c.BaseURL) + if len(*configErrors) > 0 { + return + } + + p, err := filepath.Abs(c.TemplateDir) + if err != nil { + configErrors.Add("unable to get template directory") + return + } + + c.TextTemplates = textTemplate.Must(textTemplate.New("blockEventsError").Parse(c.BlockEventsError)) + c.TextTemplates = textTemplate.Must(c.TextTemplates.New("serverNoticeTemplate").Parse(c.ServerNoticeContent.Body)) + + // Read all defined *.gohtml templates + t, err := template.ParseGlob(filepath.Join(p, "*.gohtml")) + if err != nil || t == nil { + configErrors.Add(fmt.Sprintf("unable to read consent templates: %+v", err)) + return + } + c.Templates = t + // Verify we've got a template for the defined version + versionTemplate := c.Templates.Lookup(c.Version + ".gohtml") + if versionTemplate == nil { + configErrors.Add(fmt.Sprintf("unable to load defined '%s' policy template", c.Version)) } } From cd7a7606a14cb8fe33ee3ba4d77b7837ce970f54 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Wed, 4 May 2022 14:12:52 +0200 Subject: [PATCH 46/56] Remove noise --- roomserver/storage/postgres/membership_table.go | 11 +---------- roomserver/storage/sqlite3/membership_table.go | 12 +----------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/roomserver/storage/postgres/membership_table.go b/roomserver/storage/postgres/membership_table.go index 0688c7c196..b790dac791 100644 --- a/roomserver/storage/postgres/membership_table.go +++ b/roomserver/storage/postgres/membership_table.go @@ -115,9 +115,6 @@ const updateMembershipForgetRoom = "" + const selectRoomsWithMembershipSQL = "" + "SELECT room_nid FROM roomserver_membership WHERE membership_nid = $1 AND target_nid = $2 and forgotten = false" -const selectRoomsForUserSQL = "" + - "SELECT room_nid FROM roomserver_membership WHERE target_nid = $1 and forgotten = false" - // selectKnownUsersSQL uses a sub-select statement here to find rooms that the user is // joined to. Since this information is used to populate the user directory, we will // only return users that the user would ordinarily be able to see anyway. @@ -161,7 +158,6 @@ type membershipStatements struct { updateMembershipForgetRoomStmt *sql.Stmt selectLocalServerInRoomStmt *sql.Stmt selectServerInRoomStmt *sql.Stmt - selectRoomsForUserStmt *sql.Stmt } func createMembershipTable(db *sql.DB) error { @@ -187,7 +183,6 @@ func prepareMembershipTable(db *sql.DB) (tables.Membership, error) { {&s.updateMembershipForgetRoomStmt, updateMembershipForgetRoom}, {&s.selectLocalServerInRoomStmt, selectLocalServerInRoomSQL}, {&s.selectServerInRoomStmt, selectServerInRoomSQL}, - {&s.selectRoomsForUserStmt, selectRoomsForUserSQL}, }.Prepare(db) } @@ -296,12 +291,8 @@ func (s *membershipStatements) SelectRoomsWithMembership( ctx context.Context, txn *sql.Tx, userID types.EventStateKeyNID, membershipState tables.MembershipState, ) ([]types.RoomNID, error) { - var ( - rows *sql.Rows - err error - ) stmt := sqlutil.TxStmt(txn, s.selectRoomsWithMembershipStmt) - rows, err = stmt.QueryContext(ctx, membershipState, userID) + rows, err := stmt.QueryContext(ctx, membershipState, userID) if err != nil { return nil, err diff --git a/roomserver/storage/sqlite3/membership_table.go b/roomserver/storage/sqlite3/membership_table.go index d1d59f85ff..1a111087de 100644 --- a/roomserver/storage/sqlite3/membership_table.go +++ b/roomserver/storage/sqlite3/membership_table.go @@ -91,9 +91,6 @@ const updateMembershipForgetRoom = "" + const selectRoomsWithMembershipSQL = "" + "SELECT room_nid FROM roomserver_membership WHERE membership_nid = $1 AND target_nid = $2 and forgotten = false" -const selectRoomsForUserSQL = "" + - "SELECT room_nid FROM roomserver_membership WHERE target_nid = $1 and forgotten = false" - // selectKnownUsersSQL uses a sub-select statement here to find rooms that the user is // joined to. Since this information is used to populate the user directory, we will // only return users that the user would ordinarily be able to see anyway. @@ -137,7 +134,6 @@ type membershipStatements struct { updateMembershipForgetRoomStmt *sql.Stmt selectLocalServerInRoomStmt *sql.Stmt selectServerInRoomStmt *sql.Stmt - selectRoomsForUserStmt *sql.Stmt } func createMembershipTable(db *sql.DB) error { @@ -164,7 +160,6 @@ func prepareMembershipTable(db *sql.DB) (tables.Membership, error) { {&s.updateMembershipForgetRoomStmt, updateMembershipForgetRoom}, {&s.selectLocalServerInRoomStmt, selectLocalServerInRoomSQL}, {&s.selectServerInRoomStmt, selectServerInRoomSQL}, - {&s.selectRoomsForUserStmt, selectRoomsForUserSQL}, }.Prepare(db) } @@ -273,13 +268,8 @@ func (s *membershipStatements) UpdateMembership( func (s *membershipStatements) SelectRoomsWithMembership( ctx context.Context, txn *sql.Tx, userID types.EventStateKeyNID, membershipState tables.MembershipState, ) ([]types.RoomNID, error) { - var ( - rows *sql.Rows - err error - ) - stmt := sqlutil.TxStmt(txn, s.selectRoomsWithMembershipStmt) - rows, err = stmt.QueryContext(ctx, membershipState, userID) + rows, err := stmt.QueryContext(ctx, membershipState, userID) if err != nil { return nil, err From 4d5feb25449a5cbc9acd630bd9ad7c6bd5cddb19 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Wed, 4 May 2022 14:33:51 +0200 Subject: [PATCH 47/56] Fix userapi issues --- userapi/inthttp/server.go | 2 +- userapi/storage/postgres/accounts_table.go | 43 +++++++++------------- userapi/storage/sqlite3/accounts_table.go | 39 +++++++------------- 3 files changed, 33 insertions(+), 51 deletions(-) diff --git a/userapi/inthttp/server.go b/userapi/inthttp/server.go index 5bf1b225db..805d052597 100644 --- a/userapi/inthttp/server.go +++ b/userapi/inthttp/server.go @@ -466,7 +466,7 @@ func AddRoutes(internalAPIMux *mux.Router, s api.UserInternalAPI) { } err := s.QueryPolicyVersion(req.Context(), &request, &response) if err != nil { - return util.JSONResponse{Code: http.StatusBadRequest, JSON: &response} + return util.ErrorResponse(err) } return util.JSONResponse{Code: http.StatusOK, JSON: &response} }), diff --git a/userapi/storage/postgres/accounts_table.go b/userapi/storage/postgres/accounts_table.go index 6fe3b914bb..674f0cdb21 100644 --- a/userapi/storage/postgres/accounts_table.go +++ b/userapi/storage/postgres/accounts_table.go @@ -140,7 +140,12 @@ func (s *accountsStatements) InsertAccount( createdTimeMS := time.Now().UnixNano() / 1000000 stmt := sqlutil.TxStmt(txn, s.insertAccountStmt) - _, err := stmt.ExecContext(ctx, localpart, createdTimeMS, hash, nil, accountType, policyVersion) + var err error + if accountType != api.AccountTypeAppService { + _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, nil, accountType) + } else { + _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID, accountType) + } if err != nil { return nil, err } @@ -214,10 +219,8 @@ func (s *accountsStatements) SelectNewNumericLocalpart( func (s *accountsStatements) SelectPrivacyPolicy( ctx context.Context, txn *sql.Tx, localPart string, ) (policy string, err error) { - stmt := s.selectPrivacyPolicyStmt - if txn != nil { - stmt = sqlutil.TxStmt(txn, stmt) - } + stmt := sqlutil.TxStmt(txn, s.selectPrivacyPolicyStmt) + err = stmt.QueryRowContext(ctx, localPart).Scan(&policy) return } @@ -226,11 +229,11 @@ func (s *accountsStatements) SelectPrivacyPolicy( func (s *accountsStatements) BatchSelectPrivacyPolicy( ctx context.Context, txn *sql.Tx, policyVersion string, ) (userIDs []string, err error) { - stmt := s.batchSelectPrivacyPolicyStmt - if txn != nil { - stmt = sqlutil.TxStmt(txn, stmt) - } + stmt := sqlutil.TxStmt(txn, s.batchSelectPrivacyPolicyStmt) rows, err := stmt.QueryContext(ctx, policyVersion) + if err != nil { + return nil, err + } defer internal.CloseAndLogIfError(ctx, rows, "BatchSelectPrivacyPolicy: rows.close() failed") for rows.Next() { var userID string @@ -250,9 +253,7 @@ func (s *accountsStatements) UpdatePolicyVersion( if serverNotice { stmt = s.updatePolicyVersionServerNoticeStmt } - if txn != nil { - stmt = sqlutil.TxStmt(txn, stmt) - } + stmt = sqlutil.TxStmt(txn, stmt) _, err = stmt.ExecContext(ctx, policyVersion, localpart) return err } @@ -261,31 +262,23 @@ func (s *accountsStatements) UpdatePolicyVersion( func (s *accountsStatements) SelectServerNoticeRoomID( ctx context.Context, txn *sql.Tx, localpart string, ) (roomID string, err error) { - stmt := s.selectServerNoticeRoomStmt - if txn != nil { - stmt = sqlutil.TxStmt(txn, stmt) - } + stmt := sqlutil.TxStmt(txn, s.selectServerNoticeRoomStmt) roomIDNull := sql.NullString{} row := stmt.QueryRowContext(ctx, localpart) err = row.Scan(&roomIDNull) - if err != nil { + if err != nil && err != sql.ErrNoRows { return "", err } - if roomIDNull.Valid { - return roomIDNull.String, nil - } - return "", nil + // roomIDNull.String is either the roomID or an empty string + return roomIDNull.String, nil } // UpdateServerNoticeRoomID sets the server notice room ID. func (s *accountsStatements) UpdateServerNoticeRoomID( ctx context.Context, txn *sql.Tx, localpart, roomID string, ) (err error) { - stmt := s.updateServerNoticeRoomStmt - if txn != nil { - stmt = sqlutil.TxStmt(txn, stmt) - } + stmt := sqlutil.TxStmt(txn, s.updateServerNoticeRoomStmt) _, err = stmt.ExecContext(ctx, roomID, localpart) return } diff --git a/userapi/storage/sqlite3/accounts_table.go b/userapi/storage/sqlite3/accounts_table.go index f9c2a1aeaa..6390cfc6a8 100644 --- a/userapi/storage/sqlite3/accounts_table.go +++ b/userapi/storage/sqlite3/accounts_table.go @@ -142,7 +142,12 @@ func (s *accountsStatements) InsertAccount( createdTimeMS := time.Now().UnixNano() / 1000000 stmt := s.insertAccountStmt - _, err := sqlutil.TxStmt(txn, stmt).ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID, accountType, policyVersion) + var err error + if accountType != api.AccountTypeAppService { + _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, nil, accountType) + } else { + _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID, accountType) + } if err != nil { return nil, err } @@ -220,10 +225,7 @@ func (s *accountsStatements) SelectNewNumericLocalpart( func (s *accountsStatements) SelectPrivacyPolicy( ctx context.Context, txn *sql.Tx, localPart string, ) (policy string, err error) { - stmt := s.selectPrivacyPolicyStmt - if txn != nil { - stmt = sqlutil.TxStmt(txn, stmt) - } + stmt := sqlutil.TxStmt(txn, s.selectPrivacyPolicyStmt) err = stmt.QueryRowContext(ctx, localPart).Scan(&policy) return } @@ -232,10 +234,7 @@ func (s *accountsStatements) SelectPrivacyPolicy( func (s *accountsStatements) BatchSelectPrivacyPolicy( ctx context.Context, txn *sql.Tx, policyVersion string, ) (userIDs []string, err error) { - stmt := s.batchSelectPrivacyPolicyStmt - if txn != nil { - stmt = sqlutil.TxStmt(txn, stmt) - } + stmt := sqlutil.TxStmt(txn, s.batchSelectPrivacyPolicyStmt) rows, err := stmt.QueryContext(ctx, policyVersion, policyVersion) if err != nil { return nil, err @@ -259,9 +258,7 @@ func (s *accountsStatements) UpdatePolicyVersion( if serverNotice { stmt = s.updatePolicyVersionServerNoticeStmt } - if txn != nil { - stmt = sqlutil.TxStmt(txn, stmt) - } + stmt = sqlutil.TxStmt(txn, stmt) _, err = stmt.ExecContext(ctx, policyVersion, localpart) return err } @@ -270,31 +267,23 @@ func (s *accountsStatements) UpdatePolicyVersion( func (s *accountsStatements) SelectServerNoticeRoomID( ctx context.Context, txn *sql.Tx, localpart string, ) (roomID string, err error) { - stmt := s.selectServerNoticeRoomStmt - if txn != nil { - stmt = sqlutil.TxStmt(txn, stmt) - } + stmt := sqlutil.TxStmt(txn, s.selectServerNoticeRoomStmt) roomIDNull := sql.NullString{} row := stmt.QueryRowContext(ctx, localpart) err = row.Scan(&roomIDNull) - if err != nil { + if err != nil && err != sql.ErrNoRows { return "", err } - if roomIDNull.Valid { - return roomIDNull.String, nil - } - return "", nil + // roomIDNull.String is either the roomID or an empty string + return roomIDNull.String, nil } // UpdateServerNoticeRoomID sets the server notice room ID. func (s *accountsStatements) UpdateServerNoticeRoomID( ctx context.Context, txn *sql.Tx, localpart, roomID string, ) (err error) { - stmt := s.updateServerNoticeRoomStmt - if txn != nil { - stmt = sqlutil.TxStmt(txn, stmt) - } + stmt := sqlutil.TxStmt(txn, s.updateServerNoticeRoomStmt) _, err = stmt.ExecContext(ctx, roomID, localpart) return } From 88612ddd0ca34ddca5ed8e98007d6ba5651332ac Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Wed, 4 May 2022 14:40:25 +0200 Subject: [PATCH 48/56] Deduplicate constructing consent URL --- clientapi/routing/consent_tracking.go | 21 +-------------------- internal/httputil/httpapi.go | 23 +---------------------- setup/config/config_global.go | 21 +++++++++++++++++++++ 3 files changed, 23 insertions(+), 42 deletions(-) diff --git a/clientapi/routing/consent_tracking.go b/clientapi/routing/consent_tracking.go index c3075e15b2..a71402edd8 100644 --- a/clientapi/routing/consent_tracking.go +++ b/clientapi/routing/consent_tracking.go @@ -22,7 +22,6 @@ import ( "encoding/hex" "fmt" "net/http" - "net/url" appserviceAPI "github.com/matrix-org/dendrite/appservice/api" "github.com/matrix-org/dendrite/clientapi/jsonerror" @@ -156,7 +155,7 @@ func sendServerNoticeForConsent(userAPI userapi.UserInternalAPI, rsAPI api.Rooms continue } userID := fmt.Sprintf("@%s:%s", localpart, cfgClient.Matrix.ServerName) - data["ConsentURL"], err = buildConsentURI(cfgClient, userID) + data["ConsentURL"], err = consentOpts.ConsentURL(userID) if err != nil { logrus.WithError(err).WithField("userID", userID).Error("unable to construct consentURI") continue @@ -199,24 +198,6 @@ func sendServerNoticeForConsent(userAPI userapi.UserInternalAPI, rsAPI api.Rooms } } -func buildConsentURI(cfgClient *config.ClientAPI, userID string) (string, error) { - consentOpts := cfgClient.Matrix.UserConsentOptions - - mac := hmac.New(sha256.New, []byte(consentOpts.FormSecret)) - _, err := mac.Write([]byte(userID)) - if err != nil { - return "", err - } - userMAC := mac.Sum(nil) - - params := url.Values{} - params.Add("u", userID) - params.Add("h", string(userMAC)) - params.Add("v", consentOpts.Version) - - return fmt.Sprintf("%s/_matrix/client/consent?%s", cfgClient.Matrix.UserConsentOptions.BaseURL, params.Encode()), nil -} - func validHMAC(username, userHMAC, secret string) (bool, error) { mac := hmac.New(sha256.New, []byte(secret)) _, err := mac.Write([]byte(username)) diff --git a/internal/httputil/httpapi.go b/internal/httputil/httpapi.go index 9716c4974a..4433f524e6 100644 --- a/internal/httputil/httpapi.go +++ b/internal/httputil/httpapi.go @@ -17,15 +17,11 @@ package httputil import ( "bytes" "context" - "crypto/hmac" - "crypto/sha256" - "encoding/hex" "fmt" "io" "net/http" "net/http/httptest" "net/http/httputil" - "net/url" "os" "strings" "sync" @@ -138,7 +134,7 @@ func checkConsent(ctx context.Context, userID string, userAPI userapi.UserIntern // user hasn't accepted any policy, block access. if userConsentCfg.Version != res.PolicyVersion { - uri, err := getConsentURL(userID, userConsentCfg) + uri, err := userConsentCfg.ConsentURL(userID) if err != nil { return &util.JSONResponse{ Code: http.StatusInternalServerError, @@ -166,23 +162,6 @@ func checkConsent(ctx context.Context, userID string, userAPI userapi.UserIntern return nil } -// getConsentURL constructs the URL shown to users to accept the TOS -func getConsentURL(userID string, config config.UserConsentOptions) (string, error) { - mac := hmac.New(sha256.New, []byte(config.FormSecret)) - _, err := mac.Write([]byte(userID)) - if err != nil { - return "", err - } - hmac := hex.EncodeToString(mac.Sum(nil)) - - params := url.Values{} - params.Add("u", userID) - params.Add("h", string(hmac)) - params.Add("v", config.Version) - - return fmt.Sprintf("%s/_matrix/client/consent?%s", config.BaseURL, params.Encode()), nil -} - // MakeExternalAPI turns a util.JSONRequestHandler function into an http.Handler. // This is used for APIs that are called from the internet. func MakeExternalAPI(metricsName string, f func(*http.Request) util.JSONResponse) http.Handler { diff --git a/setup/config/config_global.go b/setup/config/config_global.go index 25ea91d436..883784e307 100644 --- a/setup/config/config_global.go +++ b/setup/config/config_global.go @@ -1,9 +1,13 @@ package config import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" "fmt" "html/template" "math/rand" + "net/url" "path/filepath" textTemplate "text/template" "time" @@ -324,6 +328,23 @@ func (c *UserConsentOptions) Verify(configErrors *ConfigErrors, isMonolith bool) } } +// ConsentURL constructs the URL shown to users to accept the TOS +func (c *UserConsentOptions) ConsentURL(userID string) (string, error) { + mac := hmac.New(sha256.New, []byte(c.FormSecret)) + _, err := mac.Write([]byte(userID)) + if err != nil { + return "", err + } + userMAC := hex.EncodeToString(mac.Sum(nil)) + + params := url.Values{} + params.Add("u", userID) + params.Add("h", userMAC) + params.Add("v", c.Version) + + return fmt.Sprintf("%s/_matrix/client/consent?%s", c.BaseURL, params.Encode()), nil +} + // PresenceOptions defines possible configurations for presence events. type PresenceOptions struct { // Whether inbound presence events are allowed From 60ba4b5612a0ccddadbf5793c200789921bd78e3 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Wed, 4 May 2022 17:53:49 +0200 Subject: [PATCH 49/56] Fix stupid mistake.. and just return the NullString --- userapi/storage/postgres/accounts_table.go | 10 +++++----- userapi/storage/sqlite3/accounts_table.go | 9 +++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/userapi/storage/postgres/accounts_table.go b/userapi/storage/postgres/accounts_table.go index 674f0cdb21..a7a0ae3fa6 100644 --- a/userapi/storage/postgres/accounts_table.go +++ b/userapi/storage/postgres/accounts_table.go @@ -142,9 +142,9 @@ func (s *accountsStatements) InsertAccount( var err error if accountType != api.AccountTypeAppService { - _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, nil, accountType) + _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, nil, accountType, policyVersion) } else { - _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID, accountType) + _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID, accountType, policyVersion) } if err != nil { return nil, err @@ -219,10 +219,10 @@ func (s *accountsStatements) SelectNewNumericLocalpart( func (s *accountsStatements) SelectPrivacyPolicy( ctx context.Context, txn *sql.Tx, localPart string, ) (policy string, err error) { + var policyNull sql.NullString stmt := sqlutil.TxStmt(txn, s.selectPrivacyPolicyStmt) - - err = stmt.QueryRowContext(ctx, localPart).Scan(&policy) - return + err = stmt.QueryRowContext(ctx, localPart).Scan(&policyNull) + return policyNull.String, err } // batchSelectPrivacyPolicy queries all users which didn't accept the current policy version diff --git a/userapi/storage/sqlite3/accounts_table.go b/userapi/storage/sqlite3/accounts_table.go index 6390cfc6a8..b433e47a05 100644 --- a/userapi/storage/sqlite3/accounts_table.go +++ b/userapi/storage/sqlite3/accounts_table.go @@ -144,9 +144,9 @@ func (s *accountsStatements) InsertAccount( var err error if accountType != api.AccountTypeAppService { - _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, nil, accountType) + _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, nil, accountType, policyVersion) } else { - _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID, accountType) + _, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID, accountType, policyVersion) } if err != nil { return nil, err @@ -225,9 +225,10 @@ func (s *accountsStatements) SelectNewNumericLocalpart( func (s *accountsStatements) SelectPrivacyPolicy( ctx context.Context, txn *sql.Tx, localPart string, ) (policy string, err error) { + var policyNull sql.NullString stmt := sqlutil.TxStmt(txn, s.selectPrivacyPolicyStmt) - err = stmt.QueryRowContext(ctx, localPart).Scan(&policy) - return + err = stmt.QueryRowContext(ctx, localPart).Scan(&policyNull) + return policyNull.String, err } // batchSelectPrivacyPolicy queries all users which didn't accept the current policy version From 2eb3aab07e73953c386cddb95fdca46758a3a94c Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Wed, 4 May 2022 17:56:46 +0200 Subject: [PATCH 50/56] Split out UserConsentPolicyAPI for easier testing --- userapi/api/api.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/userapi/api/api.go b/userapi/api/api.go index b2c21f9ef4..bce67b017a 100644 --- a/userapi/api/api.go +++ b/userapi/api/api.go @@ -32,6 +32,7 @@ type UserInternalAPI interface { UserAccountAPI UserThreePIDAPI UserDeviceAPI + UserConsentPolicyAPI InputAccountData(ctx context.Context, req *InputAccountDataRequest, res *InputAccountDataResponse) error @@ -48,13 +49,16 @@ type UserInternalAPI interface { QueryPushers(ctx context.Context, req *QueryPushersRequest, res *QueryPushersResponse) error QueryPushRules(ctx context.Context, req *QueryPushRulesRequest, res *QueryPushRulesResponse) error QueryNotifications(ctx context.Context, req *QueryNotificationsRequest, res *QueryNotificationsResponse) error - QueryPolicyVersion(ctx context.Context, req *QueryPolicyVersionRequest, res *QueryPolicyVersionResponse) error - QueryOutdatedPolicy(ctx context.Context, req *QueryOutdatedPolicyRequest, res *QueryOutdatedPolicyResponse) error - PerformUpdatePolicyVersion(ctx context.Context, req *UpdatePolicyVersionRequest, res *UpdatePolicyVersionResponse) error SelectServerNoticeRoomID(ctx context.Context, req *QueryServerNoticeRoomRequest, res *QueryServerNoticeRoomResponse) (err error) UpdateServerNoticeRoomID(ctx context.Context, req *UpdateServerNoticeRoomRequest, res *UpdateServerNoticeRoomResponse) (err error) } +type UserConsentPolicyAPI interface { + QueryOutdatedPolicy(ctx context.Context, req *QueryOutdatedPolicyRequest, res *QueryOutdatedPolicyResponse) error + PerformUpdatePolicyVersion(ctx context.Context, req *UpdatePolicyVersionRequest, res *UpdatePolicyVersionResponse) error + QueryPolicyVersion(ctx context.Context, req *QueryPolicyVersionRequest, res *QueryPolicyVersionResponse) error +} + type UserDeviceAPI interface { PerformDeviceDeletion(ctx context.Context, req *PerformDeviceDeletionRequest, res *PerformDeviceDeletionResponse) error PerformLastSeenUpdate(ctx context.Context, req *PerformLastSeenUpdateRequest, res *PerformLastSeenUpdateResponse) error From 964e1cef850678590ce833c7c476dfc913f8dcb0 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Wed, 4 May 2022 17:57:29 +0200 Subject: [PATCH 51/56] Remove noise --- roomserver/storage/postgres/membership_table.go | 1 - roomserver/storage/sqlite3/membership_table.go | 1 - 2 files changed, 2 deletions(-) diff --git a/roomserver/storage/postgres/membership_table.go b/roomserver/storage/postgres/membership_table.go index b790dac791..6ed5293e4d 100644 --- a/roomserver/storage/postgres/membership_table.go +++ b/roomserver/storage/postgres/membership_table.go @@ -293,7 +293,6 @@ func (s *membershipStatements) SelectRoomsWithMembership( ) ([]types.RoomNID, error) { stmt := sqlutil.TxStmt(txn, s.selectRoomsWithMembershipStmt) rows, err := stmt.QueryContext(ctx, membershipState, userID) - if err != nil { return nil, err } diff --git a/roomserver/storage/sqlite3/membership_table.go b/roomserver/storage/sqlite3/membership_table.go index 1a111087de..7ed86b6125 100644 --- a/roomserver/storage/sqlite3/membership_table.go +++ b/roomserver/storage/sqlite3/membership_table.go @@ -270,7 +270,6 @@ func (s *membershipStatements) SelectRoomsWithMembership( ) ([]types.RoomNID, error) { stmt := sqlutil.TxStmt(txn, s.selectRoomsWithMembershipStmt) rows, err := stmt.QueryContext(ctx, membershipState, userID) - if err != nil { return nil, err } From 94ed2d3689ea4b67c694e8875923e6a9bd4675bf Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Wed, 4 May 2022 17:57:46 +0200 Subject: [PATCH 52/56] Add tests, use simple http.HandlerFunc --- clientapi/routing/consent_tracking.go | 58 ++++--- clientapi/routing/consent_tracking_test.go | 192 ++++++++++++++++++++- clientapi/routing/routing.go | 8 +- 3 files changed, 225 insertions(+), 33 deletions(-) diff --git a/clientapi/routing/consent_tracking.go b/clientapi/routing/consent_tracking.go index a71402edd8..8af8d22817 100644 --- a/clientapi/routing/consent_tracking.go +++ b/clientapi/routing/consent_tracking.go @@ -24,12 +24,10 @@ import ( "net/http" appserviceAPI "github.com/matrix-org/dendrite/appservice/api" - "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/setup/config" userapi "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/gomatrixserverlib" - "github.com/matrix-org/util" "github.com/sirupsen/logrus" ) @@ -42,9 +40,13 @@ type constentTemplateData struct { ReadOnly bool } -func consent(writer http.ResponseWriter, req *http.Request, userAPI userapi.UserInternalAPI, cfg *config.ClientAPI) *util.JSONResponse { +func writeHeaderAndText(w http.ResponseWriter, statusCode int) { + w.WriteHeader(statusCode) + _, _ = w.Write([]byte(http.StatusText(statusCode))) +} + +func consent(writer http.ResponseWriter, req *http.Request, userAPI userapi.UserConsentPolicyAPI, cfg *config.ClientAPI) { consentCfg := cfg.Matrix.UserConsentOptions - internalError := jsonerror.InternalServerError() // The data used to populate the /consent request data := constentTemplateData{ @@ -52,6 +54,7 @@ func consent(writer http.ResponseWriter, req *http.Request, userAPI userapi.User Version: req.FormValue("v"), UserHMAC: req.FormValue("h"), } + switch req.Method { case http.MethodGet: // display the privacy policy without a form @@ -59,17 +62,24 @@ func consent(writer http.ResponseWriter, req *http.Request, userAPI userapi.User // let's see if the user already consented to the current version if !data.ReadOnly { + if ok, err := validHMAC(data.UserID, data.UserHMAC, consentCfg.FormSecret); err != nil || !ok { + writeHeaderAndText(writer, http.StatusForbidden) + return + } + res := &userapi.QueryPolicyVersionResponse{} localpart, _, err := gomatrixserverlib.SplitID('@', data.UserID) if err != nil { logrus.WithError(err).Error("unable to split username") - return &internalError + writeHeaderAndText(writer, http.StatusInternalServerError) + return } if err = userAPI.QueryPolicyVersion(req.Context(), &userapi.QueryPolicyVersionRequest{ Localpart: localpart, }, res); err != nil { logrus.WithError(err).Error("unable query policy version") - return &internalError + writeHeaderAndText(writer, http.StatusInternalServerError) + return } data.HasConsented = res.PolicyVersion == consentCfg.Version } @@ -77,23 +87,24 @@ func consent(writer http.ResponseWriter, req *http.Request, userAPI userapi.User err := consentCfg.Templates.ExecuteTemplate(writer, consentCfg.Version+".gohtml", data) if err != nil { logrus.WithError(err).Error("unable to execute consent template") - return nil + writeHeaderAndText(writer, http.StatusInternalServerError) + return } - return nil case http.MethodPost: - localpart, _, err := gomatrixserverlib.SplitID('@', data.UserID) - if err != nil { - logrus.WithError(err).Error("unable to split username") - return &internalError - } - ok, err := validHMAC(data.UserID, data.UserHMAC, consentCfg.FormSecret) if err != nil || !ok { - _, err = writer.Write([]byte("invalid HMAC provided")) - if err != nil { - return &internalError + if !ok { + writeHeaderAndText(writer, http.StatusForbidden) + return } - return &internalError + writeHeaderAndText(writer, http.StatusInternalServerError) + return + } + localpart, _, err := gomatrixserverlib.SplitID('@', data.UserID) + if err != nil { + logrus.WithError(err).Error("unable to split username") + writeHeaderAndText(writer, http.StatusInternalServerError) + return } if err = userAPI.PerformUpdatePolicyVersion( req.Context(), @@ -103,11 +114,8 @@ func consent(writer http.ResponseWriter, req *http.Request, userAPI userapi.User }, &userapi.UpdatePolicyVersionResponse{}, ); err != nil { - _, err = writer.Write([]byte("unable to update database")) - if err != nil { - logrus.WithError(err).Error("unable to write to database") - } - return &internalError + writeHeaderAndText(writer, http.StatusInternalServerError) + return } // display the privacy policy without a form data.ReadOnly = false @@ -116,11 +124,9 @@ func consent(writer http.ResponseWriter, req *http.Request, userAPI userapi.User err = consentCfg.Templates.ExecuteTemplate(writer, consentCfg.Version+".gohtml", data) if err != nil { logrus.WithError(err).Error("unable to print consent template") - return &internalError + writeHeaderAndText(writer, http.StatusInternalServerError) } - return nil } - return &util.JSONResponse{Code: http.StatusOK} } func sendServerNoticeForConsent(userAPI userapi.UserInternalAPI, rsAPI api.RoomserverInternalAPI, diff --git a/clientapi/routing/consent_tracking_test.go b/clientapi/routing/consent_tracking_test.go index 3ddcad778e..75a684eba4 100644 --- a/clientapi/routing/consent_tracking_test.go +++ b/clientapi/routing/consent_tracking_test.go @@ -1,6 +1,18 @@ package routing -import "testing" +import ( + "context" + "fmt" + "html/template" + "io/ioutil" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/matrix-org/dendrite/setup/config" + userapi "github.com/matrix-org/dendrite/userapi/api" +) func Test_validHMAC(t *testing.T) { type args struct { @@ -20,7 +32,7 @@ func Test_validHMAC(t *testing.T) { wantErr: false, want: false, }, - // $ echo -n '@alice:localhost' | openssl sha256 -hmac 'helloWorld' 27m ⚑ ◒ 15:35:54 + // $ echo -n '@alice:localhost' | openssl sha256 -hmac 'helloWorld' //(stdin)= 121c9bab767ed87a3136db0c3002144dfe414720aa328d235199082e4757541e // { @@ -32,6 +44,15 @@ func Test_validHMAC(t *testing.T) { }, want: true, }, + { + name: "invalid hmac", + args: args{ + username: "@bob:localhost", + userHMAC: "121c9bab767ed87a3136db0c3002144dfe414720aa328d235199082e4757541e", + secret: "helloWorld", + }, + want: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -46,3 +67,170 @@ func Test_validHMAC(t *testing.T) { }) } } + +type dummyAPI struct { + usersConsent map[string]string +} + +func (d dummyAPI) QueryOutdatedPolicy(ctx context.Context, req *userapi.QueryOutdatedPolicyRequest, res *userapi.QueryOutdatedPolicyResponse) error { + return nil +} + +func (d dummyAPI) PerformUpdatePolicyVersion(ctx context.Context, req *userapi.UpdatePolicyVersionRequest, res *userapi.UpdatePolicyVersionResponse) error { + d.usersConsent[req.Localpart] = req.PolicyVersion + return nil +} + +func (d dummyAPI) QueryPolicyVersion(ctx context.Context, req *userapi.QueryPolicyVersionRequest, res *userapi.QueryPolicyVersionResponse) error { + res.PolicyVersion = "v2.0" + return nil +} + +const dummyTemplate = ` +{{ if .HasConsented }} +Consent given. +{{ else }} +WithoutForm + {{ if not .ReadOnly }} + With Form. + {{ end }} +{{ end }}` + +func Test_consent(t *testing.T) { + type args struct { + username string + userHMAC string + version string + method string + } + tests := []struct { + name string + args args + wantRespCode int + wantBodyContains string + }{ + { + name: "not a userID, valid hmac", + args: args{ + username: "notAuserID", + userHMAC: "7578bbface5ebb250a63935cebc05ca12060f58ebdbd271ecbc25e25a3da154d", + version: "v1.0", + method: http.MethodGet, + }, + wantRespCode: http.StatusInternalServerError, + }, + + // $ echo -n '@alice:localhost' | openssl sha256 -hmac 'helloWorld' + //(stdin)= 121c9bab767ed87a3136db0c3002144dfe414720aa328d235199082e4757541e + // + { + name: "valid hmac for alice GET, not consented", + args: args{ + username: "@alice:localhost", + userHMAC: "121c9bab767ed87a3136db0c3002144dfe414720aa328d235199082e4757541e", + version: "v1.0", + method: http.MethodGet, + }, + wantRespCode: http.StatusOK, + wantBodyContains: "With form", + }, + { + name: "alice consents successfully", + args: args{ + username: "@alice:localhost", + userHMAC: "121c9bab767ed87a3136db0c3002144dfe414720aa328d235199082e4757541e", + version: "v1.0", + method: http.MethodPost, + }, + wantRespCode: http.StatusOK, + wantBodyContains: "Consent given", + }, + { + name: "valid hmac for alice GET, new version", + args: args{ + username: "@alice:localhost", + userHMAC: "121c9bab767ed87a3136db0c3002144dfe414720aa328d235199082e4757541e", + version: "v2.0", + method: http.MethodGet, + }, + wantRespCode: http.StatusOK, + wantBodyContains: "With form", + }, + { + name: "no hmac provided for alice, read only should be displayed", + args: args{ + username: "@alice:localhost", + userHMAC: "", + version: "v1.0", + method: http.MethodGet, + }, + wantRespCode: http.StatusOK, + wantBodyContains: "WithoutForm", + }, + { + name: "alice trying to get bobs status is forbidden", + args: args{ + username: "@bob:localhost", + userHMAC: "121c9bab767ed87a3136db0c3002144dfe414720aa328d235199082e4757541e", + version: "v1.0", + method: http.MethodGet, + }, + wantRespCode: http.StatusForbidden, + wantBodyContains: "forbidden", + }, + { + name: "alice trying to consent for bob is forbidden", + args: args{ + username: "@bob:localhost", + userHMAC: "121c9bab767ed87a3136db0c3002144dfe414720aa328d235199082e4757541e", + version: "v1.0", + method: http.MethodPost, + }, + wantRespCode: http.StatusForbidden, + wantBodyContains: "forbidden", + }, + } + + userAPI := dummyAPI{ + usersConsent: map[string]string{}, + } + consentTemplates := template.Must(template.New("v1.0.gohtml").Parse(dummyTemplate)) + consentTemplates = template.Must(consentTemplates.New("v2.0.gohtml").Parse(dummyTemplate)) + userconsentOpts := config.UserConsentOptions{ + FormSecret: "helloWorld", + Version: "v1.0", + Templates: consentTemplates, + BaseURL: "http://localhost", + } + cfg := &config.ClientAPI{ + Matrix: &config.Global{ + UserConsentOptions: userconsentOpts, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + url := fmt.Sprintf("%s/consent?u=%s&v=%s&h=%s", + userconsentOpts.BaseURL, tt.args.username, tt.args.version, tt.args.userHMAC, + ) + + req := httptest.NewRequest(tt.args.method, url, nil) + w := httptest.NewRecorder() + + consent(w, req, userAPI, cfg) + + resp := w.Result() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("unable to read response body: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != tt.wantRespCode { + t.Fatalf("expected http %d, got %d", tt.wantRespCode, resp.StatusCode) + } + + if !strings.Contains(strings.ToLower(string(body)), strings.ToLower(tt.wantBodyContains)) { + t.Fatalf("expected body to contain %s, but got %s", tt.wantBodyContains, string(body)) + } + }) + } +} diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index f43ecaa806..9921e3d041 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -191,11 +191,9 @@ func Setup( // start a new go routine to send messages about consent go sendServerNoticeForConsent(userAPI, rsAPI, &cfg.Matrix.ServerNotices, cfg, serverNotificationSender, asAPI) } - publicAPIMux.Handle("/consent", - httputil.MakeHTMLAPI("consent", func(writer http.ResponseWriter, request *http.Request) *util.JSONResponse { - return consent(writer, request, userAPI, cfg) - }), - ).Methods(http.MethodGet, http.MethodPost, http.MethodOptions) + publicAPIMux.HandleFunc("/consent", func(writer http.ResponseWriter, request *http.Request) { + consent(writer, request, userAPI, cfg) + }).Methods(http.MethodGet, http.MethodPost, http.MethodOptions) } consentRequiredCheck := httputil.WithConsentCheck(cfg.Matrix.UserConsentOptions, userAPI) From 10dc02f1eace1df6c55a3e0da31675c041ded96e Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Wed, 4 May 2022 18:38:43 +0200 Subject: [PATCH 53/56] Debugging unit tests.. --- setup/base/base.go | 1 + userapi/storage/shared/storage.go | 2 ++ userapi/storage/sqlite3/storage.go | 3 +++ userapi/userapi_test.go | 1 + 4 files changed, 7 insertions(+) diff --git a/setup/base/base.go b/setup/base/base.go index 3641ad7807..10f36fee69 100644 --- a/setup/base/base.go +++ b/setup/base/base.go @@ -259,6 +259,7 @@ func (b *BaseDendrite) Close() error { func (b *BaseDendrite) DatabaseConnection(dbProperties *config.DatabaseOptions, writer sqlutil.Writer) (*sql.DB, sqlutil.Writer, error) { if dbProperties.ConnectionString != "" || b == nil { // Open a new database connection using the supplied config. + logrus.Infof("Open a new database connection using the supplied config.: %+v", dbProperties.ConnectionString) db, err := sqlutil.Open(dbProperties, writer) return db, writer, err } diff --git a/userapi/storage/shared/storage.go b/userapi/storage/shared/storage.go index fd7511a09d..aacf492c1e 100644 --- a/userapi/storage/shared/storage.go +++ b/userapi/storage/shared/storage.go @@ -27,6 +27,7 @@ import ( "time" "github.com/matrix-org/gomatrixserverlib" + "github.com/sirupsen/logrus" "golang.org/x/crypto/bcrypt" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" @@ -159,6 +160,7 @@ func (d *Database) createAccount( } } if account, err = d.Accounts.InsertAccount(ctx, txn, localpart, hash, appserviceID, policyVersion, accountType); err != nil { + logrus.WithError(err).Error("d.Accounts.InsertAccount error") return nil, sqlutil.ErrUserExists } if err = d.Profiles.InsertProfile(ctx, txn, localpart); err != nil { diff --git a/userapi/storage/sqlite3/storage.go b/userapi/storage/sqlite3/storage.go index 459718b367..504474bd12 100644 --- a/userapi/storage/sqlite3/storage.go +++ b/userapi/storage/sqlite3/storage.go @@ -19,6 +19,7 @@ import ( "time" "github.com/matrix-org/gomatrixserverlib" + "github.com/sirupsen/logrus" "github.com/matrix-org/dendrite/internal/sqlutil" "github.com/matrix-org/dendrite/setup/base" @@ -44,6 +45,7 @@ func NewDatabase(base *base.BaseDendrite, dbProperties *config.DatabaseOptions, // preparing statements for columns that don't exist yet return nil, err } + logrus.Info("created account_accounts table") deltas.LoadIsActive(m) //deltas.LoadLastSeenTSIP(m) deltas.LoadAddAccountType(m) @@ -60,6 +62,7 @@ func NewDatabase(base *base.BaseDendrite, dbProperties *config.DatabaseOptions, if err != nil { return nil, fmt.Errorf("NewSQLiteAccountsTable: %w", err) } + logrus.Info("prepared statements for accounts table") devicesTable, err := NewSQLiteDevicesTable(db, serverName) if err != nil { return nil, fmt.Errorf("NewSQLiteDevicesTable: %w", err) diff --git a/userapi/userapi_test.go b/userapi/userapi_test.go index a9e3b67e42..b541ce012a 100644 --- a/userapi/userapi_test.go +++ b/userapi/userapi_test.go @@ -73,6 +73,7 @@ func TestQueryProfile(t *testing.T) { aliceAvatarURL := "mxc://example.com/alice" aliceDisplayName := "Alice" userAPI, accountDB := MustMakeInternalAPI(t, apiTestOpts{}) + time.Sleep(time.Second) _, err := accountDB.CreateAccount(context.TODO(), "alice", "foobar", "", "", api.AccountTypeUser) if err != nil { t.Fatalf("failed to make account: %s", err) From 4bd9a73c137850325ff617ed877acd9060ca8cd1 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Fri, 6 May 2022 09:50:20 +0200 Subject: [PATCH 54/56] Fix database locked --- userapi/storage/sqlite3/accounts_table.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userapi/storage/sqlite3/accounts_table.go b/userapi/storage/sqlite3/accounts_table.go index b433e47a05..7a700a3067 100644 --- a/userapi/storage/sqlite3/accounts_table.go +++ b/userapi/storage/sqlite3/accounts_table.go @@ -140,7 +140,7 @@ func (s *accountsStatements) InsertAccount( ctx context.Context, txn *sql.Tx, localpart, hash, appserviceID, policyVersion string, accountType api.AccountType, ) (*api.Account, error) { createdTimeMS := time.Now().UnixNano() / 1000000 - stmt := s.insertAccountStmt + stmt := sqlutil.TxStmt(txn, s.insertAccountStmt) var err error if accountType != api.AccountTypeAppService { From 3d3773d3d4e980471a2fe7aed587f3843486a884 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Fri, 6 May 2022 10:15:42 +0200 Subject: [PATCH 55/56] Rename migrations so they are executed --- ...d_policy_version.go => 2022043014375800_add_policy_version.go} | 0 ...d_policy_version.go => 2022043014375800_add_policy_version.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename userapi/storage/postgres/deltas/{2022021414375800_add_policy_version.go => 2022043014375800_add_policy_version.go} (100%) rename userapi/storage/sqlite3/deltas/{2022021414375800_add_policy_version.go => 2022043014375800_add_policy_version.go} (100%) diff --git a/userapi/storage/postgres/deltas/2022021414375800_add_policy_version.go b/userapi/storage/postgres/deltas/2022043014375800_add_policy_version.go similarity index 100% rename from userapi/storage/postgres/deltas/2022021414375800_add_policy_version.go rename to userapi/storage/postgres/deltas/2022043014375800_add_policy_version.go diff --git a/userapi/storage/sqlite3/deltas/2022021414375800_add_policy_version.go b/userapi/storage/sqlite3/deltas/2022043014375800_add_policy_version.go similarity index 100% rename from userapi/storage/sqlite3/deltas/2022021414375800_add_policy_version.go rename to userapi/storage/sqlite3/deltas/2022043014375800_add_policy_version.go From 75ca5490bc789a177aa70f012d03366e6f1bc8d0 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Mon, 9 May 2022 17:21:27 +0200 Subject: [PATCH 56/56] Fix build --- clientapi/routing/consent_tracking.go | 2 +- clientapi/routing/server_notices.go | 2 +- internal/httputil/httpapi.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/clientapi/routing/consent_tracking.go b/clientapi/routing/consent_tracking.go index feeac530a1..3136756470 100644 --- a/clientapi/routing/consent_tracking.go +++ b/clientapi/routing/consent_tracking.go @@ -133,7 +133,7 @@ func sendServerNoticeForConsent(userAPI userapi.ClientUserAPI, rsAPI api.ClientR cfgNotices *config.ServerNotices, cfgClient *config.ClientAPI, senderDevice *userapi.Device, - asAPI appserviceAPI.AppServiceQueryAPI, + asAPI appserviceAPI.AppServiceInternalAPI, ) { res := &userapi.QueryOutdatedPolicyResponse{} if err := userAPI.QueryOutdatedPolicy(context.Background(), &userapi.QueryOutdatedPolicyRequest{ diff --git a/clientapi/routing/server_notices.go b/clientapi/routing/server_notices.go index 28ed989a12..40efe03a25 100644 --- a/clientapi/routing/server_notices.go +++ b/clientapi/routing/server_notices.go @@ -95,7 +95,7 @@ func sendServerNotice( cfgNotices *config.ServerNotices, cfgClient *config.ClientAPI, senderDevice *userapi.Device, - asAPI appserviceAPI.AppServiceQueryAPI, + asAPI appserviceAPI.AppServiceInternalAPI, userAPI userapi.ClientUserAPI, txnID *string, device *userapi.Device, diff --git a/internal/httputil/httpapi.go b/internal/httputil/httpapi.go index 0e5552ffe9..55cd92620a 100644 --- a/internal/httputil/httpapi.go +++ b/internal/httputil/httpapi.go @@ -28,11 +28,11 @@ import ( "github.com/getsentry/sentry-go" "github.com/matrix-org/dendrite/clientapi/auth" "github.com/matrix-org/dendrite/clientapi/jsonerror" - federationapiAPI "github.com/matrix-org/dendrite/federationapi/api" "github.com/matrix-org/dendrite/setup/config" userapi "github.com/matrix-org/dendrite/userapi/api" + "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" - opentracing "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto"