Skip to content
This repository was archived by the owner on Nov 25, 2024. It is now read-only.
Closed
Show file tree
Hide file tree
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 Feb 14, 2022
ccc11f9
Add consentAPIMux to components
S7evinK Feb 14, 2022
4da7df5
Add consent tracking template
S7evinK Feb 14, 2022
ac34386
Add missing form_secret
S7evinK Feb 14, 2022
b6ee349
Add consent tracking endpoint
S7evinK Feb 14, 2022
9583784
Add new coloumn to track accepted policy version
S7evinK Feb 14, 2022
3c5c3ea
Add methods to query the policy version
S7evinK Feb 14, 2022
a505471
Add table migrations
S7evinK Feb 14, 2022
097f1d4
Add a way to update the policy_version for a user
S7evinK Feb 14, 2022
b2045c2
Add missing yaml tag
S7evinK Feb 14, 2022
11144de
Implement consent tracking
S7evinK Feb 14, 2022
89340cf
Verify the user has given their consent, otherwise block access
S7evinK Feb 14, 2022
d19518f
Add ConsentNotGiven error
S7evinK Feb 15, 2022
f8bebe5
Add policy_version to insertAccount statement
S7evinK Feb 15, 2022
cbdbbb0
Make sure we use the correct login stages
S7evinK Feb 15, 2022
535d388
Add new login type "m.login.terms"
S7evinK Feb 15, 2022
5a0ec6e
Add policy version to create-account & mediaapi
S7evinK Feb 15, 2022
2fc1c46
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK Feb 15, 2022
6482630
Fix insert statement
S7evinK Feb 15, 2022
26accb8
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK Feb 16, 2022
74da1f0
Remove "magic" Enabled function and use simple bool
S7evinK Feb 16, 2022
9c3a1cf
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK Feb 21, 2022
2e6987f
Add missing files
S7evinK Feb 21, 2022
cb45267
Add missing migrations
S7evinK Feb 21, 2022
fb95331
Add posibility to track sent policy versions
S7evinK Feb 21, 2022
219a15c
Load templates into one variable
S7evinK Feb 21, 2022
6622fda
Add sending server notices on startup
S7evinK Feb 21, 2022
c0845ea
Update logging
S7evinK Feb 21, 2022
e2b0ff6
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK Feb 21, 2022
185cb7a
Remove BaseURL from Global
S7evinK Feb 21, 2022
61cdb71
Use typed values for Consent
S7evinK Feb 21, 2022
c2b6019
Add possibility to query all membership states
S7evinK Feb 21, 2022
dac29c1
Split migrations in to two statements
S7evinK Feb 21, 2022
0ae8293
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK Feb 21, 2022
c65eb2b
Fix query
S7evinK Feb 21, 2022
4f2d161
Remove consentMux
S7evinK Feb 21, 2022
2042303
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK Feb 22, 2022
79e1c9e
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK Feb 24, 2022
ce658ab
Fix missing params
S7evinK Feb 24, 2022
ed16a2f
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK Feb 25, 2022
2ad15f3
Rename some functions
S7evinK Feb 25, 2022
e6e6249
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK Mar 4, 2022
df7218e
Fix parameters
S7evinK Mar 4, 2022
e80ca30
Fix receipts
S7evinK Mar 4, 2022
fa26aa9
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK Mar 4, 2022
519ea13
Add AuthAPICheck and optional functional checks
S7evinK Mar 4, 2022
699617e
Add server_notice_room_id and methods to update/get it
S7evinK Mar 7, 2022
7c6a162
Remove MemberShipStateAll
S7evinK Mar 7, 2022
c7d2254
Update templates, remove default base URL
S7evinK Mar 7, 2022
dcfc0bc
URL Encode, use new method to get server notice room
S7evinK Mar 7, 2022
710007d
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK Mar 7, 2022
39d9d88
Merge branch 'main' into s7evink/consent-tracking
kegsay Mar 9, 2022
31ac3ac
Use DefaultRoomVersion as roomVersion
S7evinK Mar 16, 2022
e42ef17
Merge branch 's7evink/consent-tracking' of github.com:matrix-org/dend…
S7evinK Mar 16, 2022
b9479a6
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK Mar 16, 2022
6324c1d
Merge branch 'main' into s7evink/consent-tracking
kegsay Mar 18, 2022
019f092
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK Mar 28, 2022
f1e8d19
Fix build
S7evinK Mar 28, 2022
c99e3af
Fix linter issues
S7evinK Mar 28, 2022
2a18023
Linter again..
S7evinK Mar 28, 2022
733b601
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK Apr 4, 2022
2b496be
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK Apr 20, 2022
1f64fc7
Merge branch 's7evink/consent-tracking' of github.com:matrix-org/dend…
S7evinK Apr 20, 2022
ef62255
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK May 3, 2022
e0cdf64
Merge branch 'main' into s7evink/consent-tracking
kegsay May 3, 2022
dc8cea6
PR comments config
S7evinK May 4, 2022
cd7a760
Remove noise
S7evinK May 4, 2022
4d5feb2
Fix userapi issues
S7evinK May 4, 2022
bddf8ed
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK May 4, 2022
88612dd
Deduplicate constructing consent URL
S7evinK May 4, 2022
60ba4b5
Fix stupid mistake.. and just return the NullString
S7evinK May 4, 2022
2eb3aab
Split out UserConsentPolicyAPI for easier testing
S7evinK May 4, 2022
964e1ce
Remove noise
S7evinK May 4, 2022
94ed2d3
Add tests, use simple http.HandlerFunc
S7evinK May 4, 2022
10dc02f
Debugging unit tests..
S7evinK May 4, 2022
4d285ff
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK May 6, 2022
4bd9a73
Fix database locked
S7evinK May 6, 2022
3d3773d
Rename migrations so they are executed
S7evinK May 6, 2022
c940907
Merge branch 'main' into s7evink/consent-tracking
kegsay May 9, 2022
75ca549
Fix build
May 9, 2022
7df7a96
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
May 9, 2022
cabc5f4
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/co…
S7evinK May 19, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clientapi/auth/authtypes/logintypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ const (
LoginTypeRecaptcha = "m.login.recaptcha"
LoginTypeApplicationService = "m.login.application_service"
LoginTypeToken = "m.login.token"
LoginTypeTerms = "m.login.terms"
)
19 changes: 19 additions & 0 deletions clientapi/jsonerror/jsonerror.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -207,3 +214,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,
}
}
219 changes: 219 additions & 0 deletions clientapi/routing/consent_tracking.go
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)
Copy link
Copy Markdown
Member

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.

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
}
Loading