This repository was archived by the owner on Nov 25, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 658
Add consent tracking #2188
Closed
Closed
Add consent tracking #2188
Changes from all commits
Commits
Show all changes
82 commits
Select commit
Hold shift + click to select a range
5702b84
Add User consent configuration
S7evinK ccc11f9
Add consentAPIMux to components
S7evinK 4da7df5
Add consent tracking template
S7evinK ac34386
Add missing form_secret
S7evinK b6ee349
Add consent tracking endpoint
S7evinK 9583784
Add new coloumn to track accepted policy version
S7evinK 3c5c3ea
Add methods to query the policy version
S7evinK a505471
Add table migrations
S7evinK 097f1d4
Add a way to update the policy_version for a user
S7evinK b2045c2
Add missing yaml tag
S7evinK 11144de
Implement consent tracking
S7evinK 89340cf
Verify the user has given their consent, otherwise block access
S7evinK d19518f
Add ConsentNotGiven error
S7evinK f8bebe5
Add policy_version to insertAccount statement
S7evinK cbdbbb0
Make sure we use the correct login stages
S7evinK 535d388
Add new login type "m.login.terms"
S7evinK 5a0ec6e
Add policy version to create-account & mediaapi
S7evinK 2fc1c46
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK 6482630
Fix insert statement
S7evinK 26accb8
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK 74da1f0
Remove "magic" Enabled function and use simple bool
S7evinK 9c3a1cf
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK 2e6987f
Add missing files
S7evinK cb45267
Add missing migrations
S7evinK fb95331
Add posibility to track sent policy versions
S7evinK 219a15c
Load templates into one variable
S7evinK 6622fda
Add sending server notices on startup
S7evinK c0845ea
Update logging
S7evinK e2b0ff6
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK 185cb7a
Remove BaseURL from Global
S7evinK 61cdb71
Use typed values for Consent
S7evinK c2b6019
Add possibility to query all membership states
S7evinK dac29c1
Split migrations in to two statements
S7evinK 0ae8293
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK c65eb2b
Fix query
S7evinK 4f2d161
Remove consentMux
S7evinK 2042303
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK 79e1c9e
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK ce658ab
Fix missing params
S7evinK ed16a2f
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK 2ad15f3
Rename some functions
S7evinK e6e6249
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK df7218e
Fix parameters
S7evinK e80ca30
Fix receipts
S7evinK fa26aa9
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK 519ea13
Add AuthAPICheck and optional functional checks
S7evinK 699617e
Add server_notice_room_id and methods to update/get it
S7evinK 7c6a162
Remove MemberShipStateAll
S7evinK c7d2254
Update templates, remove default base URL
S7evinK dcfc0bc
URL Encode, use new method to get server notice room
S7evinK 710007d
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK 39d9d88
Merge branch 'main' into s7evink/consent-tracking
kegsay 31ac3ac
Use DefaultRoomVersion as roomVersion
S7evinK e42ef17
Merge branch 's7evink/consent-tracking' of github.com:matrix-org/dend…
S7evinK b9479a6
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK 6324c1d
Merge branch 'main' into s7evink/consent-tracking
kegsay 019f092
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK f1e8d19
Fix build
S7evinK c99e3af
Fix linter issues
S7evinK 2a18023
Linter again..
S7evinK 733b601
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK 2b496be
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK 1f64fc7
Merge branch 's7evink/consent-tracking' of github.com:matrix-org/dend…
S7evinK ef62255
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK e0cdf64
Merge branch 'main' into s7evink/consent-tracking
kegsay dc8cea6
PR comments config
S7evinK cd7a760
Remove noise
S7evinK 4d5feb2
Fix userapi issues
S7evinK bddf8ed
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK 88612dd
Deduplicate constructing consent URL
S7evinK 60ba4b5
Fix stupid mistake.. and just return the NullString
S7evinK 2eb3aab
Split out UserConsentPolicyAPI for easier testing
S7evinK 964e1ce
Remove noise
S7evinK 94ed2d3
Add tests, use simple http.HandlerFunc
S7evinK 10dc02f
Debugging unit tests..
S7evinK 4d285ff
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK 4bd9a73
Fix database locked
S7evinK 3d3773d
Rename migrations so they are executed
S7evinK c940907
Merge branch 'main' into s7evink/consent-tracking
kegsay 75ca549
Fix build
7df7a96
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
cabc5f4
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,219 @@ | ||
| // 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 ( | ||
| "bytes" | ||
| "context" | ||
| "crypto/hmac" | ||
| "crypto/sha256" | ||
| "encoding/hex" | ||
| "fmt" | ||
| "net/http" | ||
|
|
||
| appserviceAPI "github.com/matrix-org/dendrite/appservice/api" | ||
| "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/sirupsen/logrus" | ||
| ) | ||
|
|
||
| // The data used to populate the /consent request | ||
| type constentTemplateData struct { | ||
| UserID string | ||
| Version string | ||
| UserHMAC string | ||
| HasConsented bool | ||
| ReadOnly bool | ||
| } | ||
|
|
||
| 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 | ||
|
|
||
| // The data used to populate the /consent request | ||
| data := constentTemplateData{ | ||
| 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.ReadOnly = data.UserID == "" || data.UserHMAC == "" || data.Version == "" | ||
|
|
||
| // 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") | ||
| 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") | ||
| writeHeaderAndText(writer, http.StatusInternalServerError) | ||
| 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 execute consent template") | ||
| writeHeaderAndText(writer, http.StatusInternalServerError) | ||
| return | ||
| } | ||
| case http.MethodPost: | ||
| ok, err := validHMAC(data.UserID, data.UserHMAC, consentCfg.FormSecret) | ||
| if err != nil || !ok { | ||
| if !ok { | ||
| writeHeaderAndText(writer, http.StatusForbidden) | ||
| return | ||
| } | ||
| 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(), | ||
| &userapi.UpdatePolicyVersionRequest{ | ||
| PolicyVersion: data.Version, | ||
| Localpart: localpart, | ||
| }, | ||
| &userapi.UpdatePolicyVersionResponse{}, | ||
| ); err != nil { | ||
| writeHeaderAndText(writer, http.StatusInternalServerError) | ||
| return | ||
| } | ||
| // display the privacy policy without a form | ||
| data.ReadOnly = 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") | ||
| writeHeaderAndText(writer, http.StatusInternalServerError) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func sendServerNoticeForConsent(userAPI userapi.ClientUserAPI, rsAPI api.ClientRoomserverAPI, | ||
| cfgNotices *config.ServerNotices, | ||
| cfgClient *config.ClientAPI, | ||
| senderDevice *userapi.Device, | ||
| asAPI appserviceAPI.AppServiceInternalAPI, | ||
| ) { | ||
| 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") | ||
| return | ||
| } | ||
|
|
||
| var ( | ||
| consentOpts = cfgClient.Matrix.UserConsentOptions | ||
| data = make(map[string]string) | ||
| err error | ||
| sentMessages int | ||
| ) | ||
|
|
||
| if len(res.UserLocalparts) == 0 { | ||
| return | ||
| } | ||
|
|
||
| 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", localpart, cfgClient.Matrix.ServerName) | ||
| data["ConsentURL"], err = consentOpts.ConsentURL(userID) | ||
| if err != nil { | ||
| logrus.WithError(err).WithField("userID", userID).Error("unable to construct consentURI") | ||
| continue | ||
| } | ||
| 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, 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 | ||
| } | ||
| } | ||
| if sentMessages > 0 { | ||
| logrus.Infof("Sent messages to %d users", sentMessages) | ||
| } | ||
| } | ||
|
|
||
| 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 | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it safe to do this without checking the HMAC first? What stops me from hitting this endpoint with any user ID I like to see if they have consented or not, resulting in a privacy leak? We should surely be checking the HMAC given we know it has been set.
Assuming my understanding is correct, please add tests here to ensure that we don't leak data to unauthenticated users.