mirror of
https://github.com/matrix-org/dendrite.git
synced 2024-11-23 06:41:56 -06:00
Add sending server notices on startup
This commit is contained in:
parent
219a15c4c3
commit
6622fda08c
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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, err := getAllUserRooms(ctx, rsAPI, serverNoticeRequest.UserID)
|
||||
if err != nil {
|
||||
return util.ErrorResponse(err), nil
|
||||
}
|
||||
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 = 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) {
|
||||
|
|
Loading…
Reference in a new issue