mirror of
https://github.com/matrix-org/dendrite.git
synced 2026-01-08 14:43:09 -06:00
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/fts
This commit is contained in:
commit
a9ae73444a
1
.github/workflows/dendrite.yml
vendored
1
.github/workflows/dendrite.yml
vendored
|
|
@ -17,6 +17,7 @@ jobs:
|
||||||
name: WASM build test
|
name: WASM build test
|
||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ false }} # disable for now
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
|
|
||||||
46
CHANGES.md
46
CHANGES.md
|
|
@ -1,5 +1,51 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Dendrite 0.8.9 (2022-07-01)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Incoming device list updates over federation are now queued in JetStream for processing so that they will no longer block incoming federation transactions and should never end up dropped, which will hopefully help E2EE reliability
|
||||||
|
* The `/context` endpoint now returns `"start"` and `"end"` parameters to allow pagination from a context call
|
||||||
|
* The `/messages` endpoint will no longer return `"end"` when there are no more messages remaining
|
||||||
|
* Deactivated user accounts will now leave all rooms automatically
|
||||||
|
* New admin endpoint `/_dendrite/admin/evacuateUser/{userID}` has been added for forcing a local user to leave all joined rooms
|
||||||
|
* Dendrite will now automatically attempt to raise the file descriptor limit at startup if it is too low
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* A rare crash when retrieving remote device lists has been fixed
|
||||||
|
* Fixes a bug where events were not redacted properly over federation
|
||||||
|
* The `/invite` endpoints will now return an error instead of silently proceeding if the user ID is obviously malformed
|
||||||
|
|
||||||
|
## Dendrite 0.8.8 (2022-06-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* The performance of state resolution has been increased significantly for larger rooms
|
||||||
|
* A number of changes have been made to rate limiting:
|
||||||
|
* Logged in users will now be rate-limited on a per-session basis rather than by remote IP
|
||||||
|
* Rate limiting no longer applies to admin or appservice users
|
||||||
|
* It is now possible to configure additional users that are exempt from rate limiting using the `exempt_user_ids` option in the `rate_limiting` section of the Dendrite config
|
||||||
|
* Setting state is now idempotent via the client API state endpoints
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* Room upgrades now properly propagate tombstone events to remote servers
|
||||||
|
* Room upgrades will no longer send tombstone events if creating the upgraded room fails
|
||||||
|
* A crash has been fixed when evaluating restricted room joins
|
||||||
|
|
||||||
|
## Dendrite 0.8.7 (2022-06-01)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Support added for room version 10
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* A number of state handling bugs have been fixed, which previously resulted in missing state events, unexpected state deletions, reverted memberships and unexpectedly rejected/soft-failed events in some specific cases
|
||||||
|
* Fixed destination queue performance issues as a result of missing indexes, which speeds up outbound federation considerably
|
||||||
|
* A bug which could cause the `/register` endpoint to return HTTP 500 has been fixed
|
||||||
|
|
||||||
## Dendrite 0.8.6 (2022-05-26)
|
## Dendrite 0.8.6 (2022-05-26)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
|
||||||
2
build.sh
2
build.sh
|
|
@ -21,4 +21,4 @@ mkdir -p bin
|
||||||
|
|
||||||
CGO_ENABLED=1 go build -trimpath -ldflags "$FLAGS" -v -o "bin/" ./cmd/...
|
CGO_ENABLED=1 go build -trimpath -ldflags "$FLAGS" -v -o "bin/" ./cmd/...
|
||||||
|
|
||||||
CGO_ENABLED=0 GOOS=js GOARCH=wasm go build -trimpath -ldflags "$FLAGS" -o bin/main.wasm ./cmd/dendritejs-pinecone
|
# CGO_ENABLED=0 GOOS=js GOARCH=wasm go build -trimpath -ldflags "$FLAGS" -o bin/main.wasm ./cmd/dendritejs-pinecone
|
||||||
|
|
|
||||||
|
|
@ -261,7 +261,7 @@ func (m *DendriteMonolith) Start() {
|
||||||
cfg.MSCs.MSCs = []string{"msc2836", "msc2946"}
|
cfg.MSCs.MSCs = []string{"msc2836", "msc2946"}
|
||||||
cfg.ClientAPI.RegistrationDisabled = false
|
cfg.ClientAPI.RegistrationDisabled = false
|
||||||
cfg.ClientAPI.OpenRegistrationWithoutVerificationEnabled = true
|
cfg.ClientAPI.OpenRegistrationWithoutVerificationEnabled = true
|
||||||
if err := cfg.Derive(); err != nil {
|
if err = cfg.Derive(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -342,11 +342,23 @@ func (m *DendriteMonolith) Start() {
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
m.logger.Info("Listening on ", cfg.Global.ServerName)
|
m.logger.Info("Listening on ", cfg.Global.ServerName)
|
||||||
m.logger.Fatal(m.httpServer.Serve(m.PineconeQUIC.Protocol("matrix")))
|
|
||||||
|
switch m.httpServer.Serve(m.PineconeQUIC.Protocol("matrix")) {
|
||||||
|
case net.ErrClosed, http.ErrServerClosed:
|
||||||
|
m.logger.Info("Stopped listening on ", cfg.Global.ServerName)
|
||||||
|
default:
|
||||||
|
m.logger.Fatal(err)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
logrus.Info("Listening on ", m.listener.Addr())
|
logrus.Info("Listening on ", m.listener.Addr())
|
||||||
logrus.Fatal(http.Serve(m.listener, httpRouter))
|
|
||||||
|
switch http.Serve(m.listener, httpRouter) {
|
||||||
|
case net.ErrClosed, http.ErrServerClosed:
|
||||||
|
m.logger.Info("Stopped listening on ", cfg.Global.ServerName)
|
||||||
|
default:
|
||||||
|
m.logger.Fatal(err)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -170,11 +170,11 @@ func (m *DendriteMonolith) Start() {
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
m.logger.Info("Listening on ", ygg.DerivedServerName())
|
m.logger.Info("Listening on ", ygg.DerivedServerName())
|
||||||
m.logger.Fatal(m.httpServer.Serve(ygg))
|
m.logger.Error(m.httpServer.Serve(ygg))
|
||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
logrus.Info("Listening on ", m.listener.Addr())
|
logrus.Info("Listening on ", m.listener.Addr())
|
||||||
logrus.Fatal(http.Serve(m.listener, httpRouter))
|
logrus.Error(http.Serve(m.listener, httpRouter))
|
||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
logrus.Info("Sending wake-up message to known nodes")
|
logrus.Info("Sending wake-up message to known nodes")
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,43 @@ func AdminEvacuateRoom(req *http.Request, device *userapi.Device, rsAPI roomserv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AdminEvacuateUser(req *http.Request, device *userapi.Device, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse {
|
||||||
|
if device.AccountType != userapi.AccountTypeAdmin {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: jsonerror.Forbidden("This API can only be used by admin users."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
if err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
userID, ok := vars["userID"]
|
||||||
|
if !ok {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.MissingArgument("Expecting user ID."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res := &roomserverAPI.PerformAdminEvacuateUserResponse{}
|
||||||
|
rsAPI.PerformAdminEvacuateUser(
|
||||||
|
req.Context(),
|
||||||
|
&roomserverAPI.PerformAdminEvacuateUserRequest{
|
||||||
|
UserID: userID,
|
||||||
|
},
|
||||||
|
res,
|
||||||
|
)
|
||||||
|
if err := res.Error; err != nil {
|
||||||
|
return err.JSONResponse()
|
||||||
|
}
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 200,
|
||||||
|
JSON: map[string]interface{}{
|
||||||
|
"affected": res.Affected,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func AdminReindex(req *http.Request, cfg *config.ClientAPI, device *userapi.Device, natsClient *nats.Conn) util.JSONResponse {
|
func AdminReindex(req *http.Request, cfg *config.ClientAPI, device *userapi.Device, natsClient *nats.Conn) util.JSONResponse {
|
||||||
if device.AccountType != userapi.AccountTypeAdmin {
|
if device.AccountType != userapi.AccountTypeAdmin {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ func GetAliases(
|
||||||
return util.ErrorResponse(fmt.Errorf("rsAPI.QueryCurrentState: %w", err))
|
return util.ErrorResponse(fmt.Errorf("rsAPI.QueryCurrentState: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
visibility := "invite"
|
visibility := gomatrixserverlib.HistoryVisibilityInvited
|
||||||
if historyVisEvent, ok := stateRes.StateEvents[stateTuple]; ok {
|
if historyVisEvent, ok := stateRes.StateEvents[stateTuple]; ok {
|
||||||
var err error
|
var err error
|
||||||
visibility, err = historyVisEvent.HistoryVisibility()
|
visibility, err = historyVisEvent.HistoryVisibility()
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,10 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/tidwall/gjson"
|
|
||||||
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/gomatrixserverlib/tokens"
|
"github.com/matrix-org/gomatrixserverlib/tokens"
|
||||||
|
|
@ -68,9 +69,10 @@ const (
|
||||||
// It shouldn't be passed by value because it contains a mutex.
|
// It shouldn't be passed by value because it contains a mutex.
|
||||||
type sessionsDict struct {
|
type sessionsDict struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
sessions map[string][]authtypes.LoginType
|
sessions map[string][]authtypes.LoginType
|
||||||
params map[string]registerRequest
|
sessionCompletedResult map[string]registerResponse
|
||||||
timer map[string]*time.Timer
|
params map[string]registerRequest
|
||||||
|
timer map[string]*time.Timer
|
||||||
// deleteSessionToDeviceID protects requests to DELETE /devices/{deviceID} from being abused.
|
// deleteSessionToDeviceID protects requests to DELETE /devices/{deviceID} from being abused.
|
||||||
// If a UIA session is started by trying to delete device1, and then UIA is completed by deleting device2,
|
// If a UIA session is started by trying to delete device1, and then UIA is completed by deleting device2,
|
||||||
// the delete request will fail for device2 since the UIA was initiated by trying to delete device1.
|
// the delete request will fail for device2 since the UIA was initiated by trying to delete device1.
|
||||||
|
|
@ -115,6 +117,7 @@ func (d *sessionsDict) deleteSession(sessionID string) {
|
||||||
delete(d.params, sessionID)
|
delete(d.params, sessionID)
|
||||||
delete(d.sessions, sessionID)
|
delete(d.sessions, sessionID)
|
||||||
delete(d.deleteSessionToDeviceID, sessionID)
|
delete(d.deleteSessionToDeviceID, sessionID)
|
||||||
|
delete(d.sessionCompletedResult, sessionID)
|
||||||
// stop the timer, e.g. because the registration was completed
|
// stop the timer, e.g. because the registration was completed
|
||||||
if t, ok := d.timer[sessionID]; ok {
|
if t, ok := d.timer[sessionID]; ok {
|
||||||
if !t.Stop() {
|
if !t.Stop() {
|
||||||
|
|
@ -130,6 +133,7 @@ func (d *sessionsDict) deleteSession(sessionID string) {
|
||||||
func newSessionsDict() *sessionsDict {
|
func newSessionsDict() *sessionsDict {
|
||||||
return &sessionsDict{
|
return &sessionsDict{
|
||||||
sessions: make(map[string][]authtypes.LoginType),
|
sessions: make(map[string][]authtypes.LoginType),
|
||||||
|
sessionCompletedResult: make(map[string]registerResponse),
|
||||||
params: make(map[string]registerRequest),
|
params: make(map[string]registerRequest),
|
||||||
timer: make(map[string]*time.Timer),
|
timer: make(map[string]*time.Timer),
|
||||||
deleteSessionToDeviceID: make(map[string]string),
|
deleteSessionToDeviceID: make(map[string]string),
|
||||||
|
|
@ -173,6 +177,19 @@ func (d *sessionsDict) addDeviceToDelete(sessionID, deviceID string) {
|
||||||
d.deleteSessionToDeviceID[sessionID] = deviceID
|
d.deleteSessionToDeviceID[sessionID] = deviceID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *sessionsDict) addCompletedRegistration(sessionID string, response registerResponse) {
|
||||||
|
d.Lock()
|
||||||
|
defer d.Unlock()
|
||||||
|
d.sessionCompletedResult[sessionID] = response
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sessionsDict) getCompletedRegistration(sessionID string) (registerResponse, bool) {
|
||||||
|
d.RLock()
|
||||||
|
defer d.RUnlock()
|
||||||
|
result, ok := d.sessionCompletedResult[sessionID]
|
||||||
|
return result, ok
|
||||||
|
}
|
||||||
|
|
||||||
func (d *sessionsDict) getDeviceToDelete(sessionID string) (string, bool) {
|
func (d *sessionsDict) getDeviceToDelete(sessionID string) (string, bool) {
|
||||||
d.RLock()
|
d.RLock()
|
||||||
defer d.RUnlock()
|
defer d.RUnlock()
|
||||||
|
|
@ -544,6 +561,14 @@ func Register(
|
||||||
r.DeviceID = data.DeviceID
|
r.DeviceID = data.DeviceID
|
||||||
r.InitialDisplayName = data.InitialDisplayName
|
r.InitialDisplayName = data.InitialDisplayName
|
||||||
r.InhibitLogin = data.InhibitLogin
|
r.InhibitLogin = data.InhibitLogin
|
||||||
|
// Check if the user already registered using this session, if so, return that result
|
||||||
|
if response, ok := sessions.getCompletedRegistration(sessionID); ok {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: response,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if resErr := httputil.UnmarshalJSON(reqBody, &r); resErr != nil {
|
if resErr := httputil.UnmarshalJSON(reqBody, &r); resErr != nil {
|
||||||
return *resErr
|
return *resErr
|
||||||
|
|
@ -839,13 +864,6 @@ func completeRegistration(
|
||||||
displayName, deviceID *string,
|
displayName, deviceID *string,
|
||||||
accType userapi.AccountType,
|
accType userapi.AccountType,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
var registrationOK bool
|
|
||||||
defer func() {
|
|
||||||
if registrationOK {
|
|
||||||
sessions.deleteSession(sessionID)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if username == "" {
|
if username == "" {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
|
|
@ -886,7 +904,6 @@ func completeRegistration(
|
||||||
// Check whether inhibit_login option is set. If so, don't create an access
|
// Check whether inhibit_login option is set. If so, don't create an access
|
||||||
// token or a device for this user
|
// token or a device for this user
|
||||||
if inhibitLogin {
|
if inhibitLogin {
|
||||||
registrationOK = true
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
JSON: registerResponse{
|
JSON: registerResponse{
|
||||||
|
|
@ -920,15 +937,17 @@ func completeRegistration(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registrationOK = true
|
result := registerResponse{
|
||||||
|
UserID: devRes.Device.UserID,
|
||||||
|
AccessToken: devRes.Device.AccessToken,
|
||||||
|
HomeServer: accRes.Account.ServerName,
|
||||||
|
DeviceID: devRes.Device.ID,
|
||||||
|
}
|
||||||
|
sessions.addCompletedRegistration(sessionID, result)
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
JSON: registerResponse{
|
JSON: result,
|
||||||
UserID: devRes.Device.UserID,
|
|
||||||
AccessToken: devRes.Device.AccessToken,
|
|
||||||
HomeServer: accRes.Account.ServerName,
|
|
||||||
DeviceID: devRes.Device.ID,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,9 @@ func Setup(
|
||||||
"r0.4.0",
|
"r0.4.0",
|
||||||
"r0.5.0",
|
"r0.5.0",
|
||||||
"r0.6.1",
|
"r0.6.1",
|
||||||
|
"v1.0",
|
||||||
|
"v1.1",
|
||||||
|
"v1.2",
|
||||||
}, UnstableFeatures: unstableFeatures},
|
}, UnstableFeatures: unstableFeatures},
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
@ -126,6 +129,12 @@ func Setup(
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
|
dendriteAdminRouter.Handle("/admin/evacuateUser/{userID}",
|
||||||
|
httputil.MakeAuthAPI("admin_evacuate_user", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
|
return AdminEvacuateUser(req, device, rsAPI)
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
dendriteAdminRouter.Handle("/admin/fulltext/reindex",
|
dendriteAdminRouter.Handle("/admin/fulltext/reindex",
|
||||||
httputil.MakeAuthAPI("admin_fultext_reindex", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("admin_fultext_reindex", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
return AdminReindex(req, cfg, device, natsClient)
|
return AdminReindex(req, cfg, device, natsClient)
|
||||||
|
|
@ -143,7 +152,7 @@ func Setup(
|
||||||
synapseAdminRouter.Handle("/admin/v1/send_server_notice/{txnID}",
|
synapseAdminRouter.Handle("/admin/v1/send_server_notice/{txnID}",
|
||||||
httputil.MakeAuthAPI("send_server_notice", userAPI, 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
|
// not specced, but ensure we're rate limiting requests to this endpoint
|
||||||
if r := rateLimits.Limit(req); r != nil {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
@ -163,7 +172,7 @@ func Setup(
|
||||||
synapseAdminRouter.Handle("/admin/v1/send_server_notice",
|
synapseAdminRouter.Handle("/admin/v1/send_server_notice",
|
||||||
httputil.MakeAuthAPI("send_server_notice", userAPI, 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
|
// not specced, but ensure we're rate limiting requests to this endpoint
|
||||||
if r := rateLimits.Limit(req); r != nil {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
return SendServerNotice(
|
return SendServerNotice(
|
||||||
|
|
@ -193,7 +202,7 @@ func Setup(
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
v3mux.Handle("/join/{roomIDOrAlias}",
|
v3mux.Handle("/join/{roomIDOrAlias}",
|
||||||
httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, 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 {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
@ -209,7 +218,7 @@ func Setup(
|
||||||
if mscCfg.Enabled("msc2753") {
|
if mscCfg.Enabled("msc2753") {
|
||||||
v3mux.Handle("/peek/{roomIDOrAlias}",
|
v3mux.Handle("/peek/{roomIDOrAlias}",
|
||||||
httputil.MakeAuthAPI(gomatrixserverlib.Peek, userAPI, 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 {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
@ -229,7 +238,7 @@ func Setup(
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
v3mux.Handle("/rooms/{roomID}/join",
|
v3mux.Handle("/rooms/{roomID}/join",
|
||||||
httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, 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 {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
@ -243,7 +252,7 @@ func Setup(
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
v3mux.Handle("/rooms/{roomID}/leave",
|
v3mux.Handle("/rooms/{roomID}/leave",
|
||||||
httputil.MakeAuthAPI("membership", userAPI, 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 {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
@ -277,7 +286,7 @@ func Setup(
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
v3mux.Handle("/rooms/{roomID}/invite",
|
v3mux.Handle("/rooms/{roomID}/invite",
|
||||||
httputil.MakeAuthAPI("membership", userAPI, 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 {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
@ -395,14 +404,14 @@ func Setup(
|
||||||
).Methods(http.MethodPut, http.MethodOptions)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
|
||||||
v3mux.Handle("/register", httputil.MakeExternalAPI("register", func(req *http.Request) util.JSONResponse {
|
v3mux.Handle("/register", httputil.MakeExternalAPI("register", func(req *http.Request) util.JSONResponse {
|
||||||
if r := rateLimits.Limit(req); r != nil {
|
if r := rateLimits.Limit(req, nil); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
return Register(req, userAPI, cfg)
|
return Register(req, userAPI, cfg)
|
||||||
})).Methods(http.MethodPost, http.MethodOptions)
|
})).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
|
||||||
v3mux.Handle("/register/available", httputil.MakeExternalAPI("registerAvailable", func(req *http.Request) util.JSONResponse {
|
v3mux.Handle("/register/available", httputil.MakeExternalAPI("registerAvailable", func(req *http.Request) util.JSONResponse {
|
||||||
if r := rateLimits.Limit(req); r != nil {
|
if r := rateLimits.Limit(req, nil); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
return RegisterAvailable(req, cfg, userAPI)
|
return RegisterAvailable(req, cfg, userAPI)
|
||||||
|
|
@ -476,7 +485,7 @@ func Setup(
|
||||||
|
|
||||||
v3mux.Handle("/rooms/{roomID}/typing/{userID}",
|
v3mux.Handle("/rooms/{roomID}/typing/{userID}",
|
||||||
httputil.MakeAuthAPI("rooms_typing", userAPI, 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 {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
@ -533,7 +542,7 @@ func Setup(
|
||||||
|
|
||||||
v3mux.Handle("/account/whoami",
|
v3mux.Handle("/account/whoami",
|
||||||
httputil.MakeAuthAPI("whoami", userAPI, 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 {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
return Whoami(req, device)
|
return Whoami(req, device)
|
||||||
|
|
@ -542,7 +551,7 @@ func Setup(
|
||||||
|
|
||||||
v3mux.Handle("/account/password",
|
v3mux.Handle("/account/password",
|
||||||
httputil.MakeAuthAPI("password", userAPI, 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 {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
return Password(req, userAPI, device, cfg)
|
return Password(req, userAPI, device, cfg)
|
||||||
|
|
@ -551,7 +560,7 @@ func Setup(
|
||||||
|
|
||||||
v3mux.Handle("/account/deactivate",
|
v3mux.Handle("/account/deactivate",
|
||||||
httputil.MakeAuthAPI("deactivate", userAPI, 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 {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
return Deactivate(req, userInteractiveAuth, userAPI, device)
|
return Deactivate(req, userInteractiveAuth, userAPI, device)
|
||||||
|
|
@ -562,7 +571,7 @@ func Setup(
|
||||||
|
|
||||||
v3mux.Handle("/login",
|
v3mux.Handle("/login",
|
||||||
httputil.MakeExternalAPI("login", func(req *http.Request) util.JSONResponse {
|
httputil.MakeExternalAPI("login", func(req *http.Request) util.JSONResponse {
|
||||||
if r := rateLimits.Limit(req); r != nil {
|
if r := rateLimits.Limit(req, nil); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
return Login(req, userAPI, cfg)
|
return Login(req, userAPI, cfg)
|
||||||
|
|
@ -670,7 +679,7 @@ func Setup(
|
||||||
|
|
||||||
v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}",
|
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, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
if r := rateLimits.Limit(req); r != nil {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
@ -736,7 +745,7 @@ func Setup(
|
||||||
|
|
||||||
v3mux.Handle("/profile/{userID}/avatar_url",
|
v3mux.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, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
if r := rateLimits.Limit(req); r != nil {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
@ -761,7 +770,7 @@ func Setup(
|
||||||
|
|
||||||
v3mux.Handle("/profile/{userID}/displayname",
|
v3mux.Handle("/profile/{userID}/displayname",
|
||||||
httputil.MakeAuthAPI("profile_displayname", userAPI, 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 {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
@ -800,7 +809,7 @@ func Setup(
|
||||||
|
|
||||||
v3mux.Handle("/voip/turnServer",
|
v3mux.Handle("/voip/turnServer",
|
||||||
httputil.MakeAuthAPI("turn_server", userAPI, 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 {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
return RequestTurnServer(req, device, cfg)
|
return RequestTurnServer(req, device, cfg)
|
||||||
|
|
@ -879,7 +888,7 @@ func Setup(
|
||||||
|
|
||||||
v3mux.Handle("/user/{userID}/openid/request_token",
|
v3mux.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, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
if r := rateLimits.Limit(req); r != nil {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
@ -892,7 +901,7 @@ func Setup(
|
||||||
|
|
||||||
v3mux.Handle("/user_directory/search",
|
v3mux.Handle("/user_directory/search",
|
||||||
httputil.MakeAuthAPI("userdirectory_search", userAPI, 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 {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
postContent := struct {
|
postContent := struct {
|
||||||
|
|
@ -938,7 +947,7 @@ func Setup(
|
||||||
|
|
||||||
v3mux.Handle("/rooms/{roomID}/read_markers",
|
v3mux.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, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
if r := rateLimits.Limit(req); r != nil {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
@ -951,7 +960,7 @@ func Setup(
|
||||||
|
|
||||||
v3mux.Handle("/rooms/{roomID}/forget",
|
v3mux.Handle("/rooms/{roomID}/forget",
|
||||||
httputil.MakeAuthAPI("rooms_forget", userAPI, 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 {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
@ -1028,7 +1037,7 @@ func Setup(
|
||||||
|
|
||||||
v3mux.Handle("/pushers/set",
|
v3mux.Handle("/pushers/set",
|
||||||
httputil.MakeAuthAPI("set_pushers", userAPI, 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 {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
return SetPusher(req, device, userAPI)
|
return SetPusher(req, device, userAPI)
|
||||||
|
|
@ -1086,7 +1095,7 @@ func Setup(
|
||||||
|
|
||||||
v3mux.Handle("/capabilities",
|
v3mux.Handle("/capabilities",
|
||||||
httputil.MakeAuthAPI("capabilities", userAPI, 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 {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
return GetCapabilities(req, rsAPI)
|
return GetCapabilities(req, rsAPI)
|
||||||
|
|
@ -1302,7 +1311,7 @@ func Setup(
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
v3mux.Handle("/rooms/{roomId}/receipt/{receiptType}/{eventId}",
|
v3mux.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, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
if r := rateLimits.Limit(req); r != nil {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -96,14 +97,21 @@ func SendEvent(
|
||||||
mutex.(*sync.Mutex).Lock()
|
mutex.(*sync.Mutex).Lock()
|
||||||
defer mutex.(*sync.Mutex).Unlock()
|
defer mutex.(*sync.Mutex).Unlock()
|
||||||
|
|
||||||
startedGeneratingEvent := time.Now()
|
|
||||||
|
|
||||||
var r map[string]interface{} // must be a JSON object
|
var r map[string]interface{} // must be a JSON object
|
||||||
resErr := httputil.UnmarshalJSONRequest(req, &r)
|
resErr := httputil.UnmarshalJSONRequest(req, &r)
|
||||||
if resErr != nil {
|
if resErr != nil {
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if stateKey != nil {
|
||||||
|
// If the existing/new state content are equal, return the existing event_id, making the request idempotent.
|
||||||
|
if resp := stateEqual(req.Context(), rsAPI, eventType, *stateKey, roomID, r); resp != nil {
|
||||||
|
return *resp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startedGeneratingEvent := time.Now()
|
||||||
|
|
||||||
// If we're sending a membership update, make sure to strip the authorised
|
// If we're sending a membership update, make sure to strip the authorised
|
||||||
// via key if it is present, otherwise other servers won't be able to auth
|
// via key if it is present, otherwise other servers won't be able to auth
|
||||||
// the event if the room is set to the "restricted" join rule.
|
// the event if the room is set to the "restricted" join rule.
|
||||||
|
|
@ -208,6 +216,37 @@ func SendEvent(
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stateEqual compares the new and the existing state event content. If they are equal, returns a *util.JSONResponse
|
||||||
|
// with the existing event_id, making this an idempotent request.
|
||||||
|
func stateEqual(ctx context.Context, rsAPI api.ClientRoomserverAPI, eventType, stateKey, roomID string, newContent map[string]interface{}) *util.JSONResponse {
|
||||||
|
stateRes := api.QueryCurrentStateResponse{}
|
||||||
|
tuple := gomatrixserverlib.StateKeyTuple{
|
||||||
|
EventType: eventType,
|
||||||
|
StateKey: stateKey,
|
||||||
|
}
|
||||||
|
err := rsAPI.QueryCurrentState(ctx, &api.QueryCurrentStateRequest{
|
||||||
|
RoomID: roomID,
|
||||||
|
StateTuples: []gomatrixserverlib.StateKeyTuple{tuple},
|
||||||
|
}, &stateRes)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if existingEvent, ok := stateRes.StateEvents[tuple]; ok {
|
||||||
|
var existingContent map[string]interface{}
|
||||||
|
if err = json.Unmarshal(existingEvent.Content(), &existingContent); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if reflect.DeepEqual(existingContent, newContent) {
|
||||||
|
return &util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: sendEventResponse{existingEvent.EventID()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func generateSendEvent(
|
func generateSendEvent(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
r map[string]interface{},
|
r map[string]interface{},
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,9 @@ import (
|
||||||
|
|
||||||
var roomVersion = flag.String("roomversion", "5", "the room version to parse events as")
|
var roomVersion = flag.String("roomversion", "5", "the room version to parse events as")
|
||||||
var filterType = flag.String("filtertype", "", "the event types to filter on")
|
var filterType = flag.String("filtertype", "", "the event types to filter on")
|
||||||
|
var difference = flag.Bool("difference", false, "whether to calculate the difference between snapshots")
|
||||||
|
|
||||||
|
// nolint:gocyclo
|
||||||
func main() {
|
func main() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
cfg := setup.ParseFlags(true)
|
cfg := setup.ParseFlags(true)
|
||||||
|
|
@ -36,6 +38,7 @@ func main() {
|
||||||
Type: "std",
|
Type: "std",
|
||||||
Level: "error",
|
Level: "error",
|
||||||
})
|
})
|
||||||
|
cfg.ClientAPI.RegistrationDisabled = true
|
||||||
base := base.NewBaseDendrite(cfg, "ResolveState", base.DisableMetrics)
|
base := base.NewBaseDendrite(cfg, "ResolveState", base.DisableMetrics)
|
||||||
args := flag.Args()
|
args := flag.Args()
|
||||||
|
|
||||||
|
|
@ -64,6 +67,59 @@ func main() {
|
||||||
RoomVersion: gomatrixserverlib.RoomVersion(*roomVersion),
|
RoomVersion: gomatrixserverlib.RoomVersion(*roomVersion),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if *difference {
|
||||||
|
if len(snapshotNIDs) != 2 {
|
||||||
|
panic("need exactly two state snapshot NIDs to calculate difference")
|
||||||
|
}
|
||||||
|
var removed, added []types.StateEntry
|
||||||
|
removed, added, err = stateres.DifferenceBetweeenStateSnapshots(ctx, snapshotNIDs[0], snapshotNIDs[1])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var eventNIDs []types.EventNID
|
||||||
|
for _, entry := range append(removed, added...) {
|
||||||
|
eventNIDs = append(eventNIDs, entry.EventNID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var eventEntries []types.Event
|
||||||
|
eventEntries, err = roomserverDB.Events(ctx, eventNIDs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
events := make(map[types.EventNID]*gomatrixserverlib.Event, len(eventEntries))
|
||||||
|
for _, entry := range eventEntries {
|
||||||
|
events[entry.EventNID] = entry.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(removed) > 0 {
|
||||||
|
fmt.Println("Removed:")
|
||||||
|
for _, r := range removed {
|
||||||
|
event := events[r.EventNID]
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("* %s %s %q\n", event.EventID(), event.Type(), *event.StateKey())
|
||||||
|
fmt.Printf(" %s\n", string(event.Content()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(removed) > 0 && len(added) > 0 {
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(added) > 0 {
|
||||||
|
fmt.Println("Added:")
|
||||||
|
for _, a := range added {
|
||||||
|
event := events[a.EventNID]
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("* %s %s %q\n", event.EventID(), event.Type(), *event.StateKey())
|
||||||
|
fmt.Printf(" %s\n", string(event.Content()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var stateEntries []types.StateEntry
|
var stateEntries []types.StateEntry
|
||||||
for _, snapshotNID := range snapshotNIDs {
|
for _, snapshotNID := range snapshotNIDs {
|
||||||
var entries []types.StateEntry
|
var entries []types.StateEntry
|
||||||
|
|
|
||||||
|
|
@ -160,11 +160,14 @@ client_api:
|
||||||
|
|
||||||
# Settings for rate-limited endpoints. Rate limiting kicks in after the threshold
|
# Settings for rate-limited endpoints. Rate limiting kicks in after the threshold
|
||||||
# number of "slots" have been taken by requests from a specific host. Each "slot"
|
# number of "slots" have been taken by requests from a specific host. Each "slot"
|
||||||
# will be released after the cooloff time in milliseconds.
|
# will be released after the cooloff time in milliseconds. Server administrators
|
||||||
|
# and appservice users are exempt from rate limiting by default.
|
||||||
rate_limiting:
|
rate_limiting:
|
||||||
enabled: true
|
enabled: true
|
||||||
threshold: 5
|
threshold: 5
|
||||||
cooloff_ms: 500
|
cooloff_ms: 500
|
||||||
|
exempt_user_ids:
|
||||||
|
# - "@user:domain.com"
|
||||||
|
|
||||||
# Configuration for the Federation API.
|
# Configuration for the Federation API.
|
||||||
federation_api:
|
federation_api:
|
||||||
|
|
|
||||||
|
|
@ -163,11 +163,14 @@ client_api:
|
||||||
|
|
||||||
# Settings for rate-limited endpoints. Rate limiting kicks in after the threshold
|
# Settings for rate-limited endpoints. Rate limiting kicks in after the threshold
|
||||||
# number of "slots" have been taken by requests from a specific host. Each "slot"
|
# number of "slots" have been taken by requests from a specific host. Each "slot"
|
||||||
# will be released after the cooloff time in milliseconds.
|
# will be released after the cooloff time in milliseconds. Server administrators
|
||||||
|
# and appservice users are exempt from rate limiting by default.
|
||||||
rate_limiting:
|
rate_limiting:
|
||||||
enabled: true
|
enabled: true
|
||||||
threshold: 5
|
threshold: 5
|
||||||
cooloff_ms: 500
|
cooloff_ms: 500
|
||||||
|
exempt_user_ids:
|
||||||
|
# - "@user:domain.com"
|
||||||
|
|
||||||
# Configuration for the Federation API.
|
# Configuration for the Federation API.
|
||||||
federation_api:
|
federation_api:
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,12 @@ This endpoint will instruct Dendrite to part all local users from the given `roo
|
||||||
in the URL. It may take some time to complete. A JSON body will be returned containing
|
in the URL. It may take some time to complete. A JSON body will be returned containing
|
||||||
the user IDs of all affected users.
|
the user IDs of all affected users.
|
||||||
|
|
||||||
|
## `/_dendrite/admin/evacuateUser/{userID}`
|
||||||
|
|
||||||
|
This endpoint will instruct Dendrite to part the given local `userID` in the URL from
|
||||||
|
all rooms which they are currently joined. A JSON body will be returned containing
|
||||||
|
the room IDs of all affected rooms.
|
||||||
|
|
||||||
## `/_synapse/admin/v1/register`
|
## `/_synapse/admin/v1/register`
|
||||||
|
|
||||||
Shared secret registration — please see the [user creation page](createusers) for
|
Shared secret registration — please see the [user creation page](createusers) for
|
||||||
|
|
|
||||||
68
docs/caddy/monolith/CaddyFile
Normal file
68
docs/caddy/monolith/CaddyFile
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
{
|
||||||
|
# debug
|
||||||
|
admin off
|
||||||
|
email example@example.com
|
||||||
|
default_sni example.com
|
||||||
|
# Debug endpoint
|
||||||
|
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
|
||||||
|
}
|
||||||
|
|
||||||
|
#######################################################################
|
||||||
|
# Snippets
|
||||||
|
#______________________________________________________________________
|
||||||
|
|
||||||
|
(handle_errors_maintenance) {
|
||||||
|
handle_errors {
|
||||||
|
@maintenance expression {http.error.status_code} == 502
|
||||||
|
rewrite @maintenance maintenance.html
|
||||||
|
root * "/path/to/service/pages"
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(matrix-well-known-header) {
|
||||||
|
# Headers
|
||||||
|
header Access-Control-Allow-Origin "*"
|
||||||
|
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
|
||||||
|
header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization"
|
||||||
|
header Content-Type "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
#######################################################################
|
||||||
|
|
||||||
|
example.com {
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
handle /.well-known/matrix/server {
|
||||||
|
import matrix-well-known-header
|
||||||
|
respond `{ "m.server": "matrix.example.com:443" }` 200
|
||||||
|
}
|
||||||
|
|
||||||
|
handle /.well-known/matrix/client {
|
||||||
|
import matrix-well-known-header
|
||||||
|
respond `{ "m.homeserver": { "base_url": "https://matrix.example.com" } }` 200
|
||||||
|
}
|
||||||
|
|
||||||
|
import handle_errors_maintenance
|
||||||
|
}
|
||||||
|
|
||||||
|
example.com:8448 {
|
||||||
|
# server<->server HTTPS traffic
|
||||||
|
reverse_proxy http://dendrite-host:8008
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix.example.com {
|
||||||
|
|
||||||
|
handle /_matrix/* {
|
||||||
|
# client<->server HTTPS traffic
|
||||||
|
reverse_proxy http://dendrite-host:8008
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_path /* {
|
||||||
|
# Client webapp (Element SPA or ...)
|
||||||
|
file_server {
|
||||||
|
root /path/to/www/example.com/matrix-web-client/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
71
docs/installation/10_optimisation.md
Normal file
71
docs/installation/10_optimisation.md
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
---
|
||||||
|
title: Optimise your installation
|
||||||
|
parent: Installation
|
||||||
|
has_toc: true
|
||||||
|
nav_order: 10
|
||||||
|
permalink: /installation/start/optimisation
|
||||||
|
---
|
||||||
|
|
||||||
|
# Optimise your installation
|
||||||
|
|
||||||
|
Now that you have Dendrite running, the following tweaks will improve the reliability
|
||||||
|
and performance of your installation.
|
||||||
|
|
||||||
|
## File descriptor limit
|
||||||
|
|
||||||
|
Most platforms have a limit on how many file descriptors a single process can open. All
|
||||||
|
connections made by Dendrite consume file descriptors — this includes database connections
|
||||||
|
and network requests to remote homeservers. When participating in large federated rooms
|
||||||
|
where Dendrite must talk to many remote servers, it is often very easy to exhaust default
|
||||||
|
limits which are quite low.
|
||||||
|
|
||||||
|
We currently recommend setting the file descriptor limit to 65535 to avoid such
|
||||||
|
issues. Dendrite will log immediately after startup if the file descriptor limit is too low:
|
||||||
|
|
||||||
|
```
|
||||||
|
level=warning msg="IMPORTANT: Process file descriptor limit is currently 1024, it is recommended to raise the limit for Dendrite to at least 65535 to avoid issues"
|
||||||
|
```
|
||||||
|
|
||||||
|
UNIX systems have two limits: a hard limit and a soft limit. You can view the soft limit
|
||||||
|
by running `ulimit -Sn` and the hard limit with `ulimit -Hn`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ulimit -Hn
|
||||||
|
1048576
|
||||||
|
|
||||||
|
$ ulimit -Sn
|
||||||
|
1024
|
||||||
|
```
|
||||||
|
|
||||||
|
Increase the soft limit before starting Dendrite:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ulimit -Sn 65535
|
||||||
|
```
|
||||||
|
|
||||||
|
The log line at startup should no longer appear if the limit is sufficient.
|
||||||
|
|
||||||
|
If you are running under a systemd service, you can instead add `LimitNOFILE=65535` option
|
||||||
|
to the `[Service]` section of your service unit file.
|
||||||
|
|
||||||
|
## DNS caching
|
||||||
|
|
||||||
|
Dendrite has a built-in DNS cache which significantly reduces the load that Dendrite will
|
||||||
|
place on your DNS resolver. This may also speed up outbound federation.
|
||||||
|
|
||||||
|
Consider enabling the DNS cache by modifying the `global` section of your configuration file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dns_cache:
|
||||||
|
enabled: true
|
||||||
|
cache_size: 4096
|
||||||
|
cache_lifetime: 600s
|
||||||
|
```
|
||||||
|
|
||||||
|
## Time synchronisation
|
||||||
|
|
||||||
|
Matrix relies heavily on TLS which requires the system time to be correct. If the clock
|
||||||
|
drifts then you may find that federation no works reliably (or at all) and clients may
|
||||||
|
struggle to connect to your Dendrite server.
|
||||||
|
|
||||||
|
Ensure that the time is synchronised on your system by enabling NTP sync.
|
||||||
|
|
@ -133,7 +133,7 @@ func (t *OutputPresenceConsumer) onMessage(ctx context.Context, msg *nats.Msg) b
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("sending presence EDU to %d servers", len(joined))
|
log.Tracef("sending presence EDU to %d servers", len(joined))
|
||||||
if err = t.queues.SendEDU(edu, t.ServerName, joined); err != nil {
|
if err = t.queues.SendEDU(edu, t.ServerName, joined); err != nil {
|
||||||
log.WithError(err).Error("failed to send EDU")
|
log.WithError(err).Error("failed to send EDU")
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ func AddPublicRoutes(
|
||||||
TopicSendToDeviceEvent: cfg.Matrix.JetStream.Prefixed(jetstream.OutputSendToDeviceEvent),
|
TopicSendToDeviceEvent: cfg.Matrix.JetStream.Prefixed(jetstream.OutputSendToDeviceEvent),
|
||||||
TopicTypingEvent: cfg.Matrix.JetStream.Prefixed(jetstream.OutputTypingEvent),
|
TopicTypingEvent: cfg.Matrix.JetStream.Prefixed(jetstream.OutputTypingEvent),
|
||||||
TopicPresenceEvent: cfg.Matrix.JetStream.Prefixed(jetstream.OutputPresenceEvent),
|
TopicPresenceEvent: cfg.Matrix.JetStream.Prefixed(jetstream.OutputPresenceEvent),
|
||||||
|
TopicDeviceListUpdate: cfg.Matrix.JetStream.Prefixed(jetstream.InputDeviceListUpdate),
|
||||||
ServerName: cfg.Matrix.ServerName,
|
ServerName: cfg.Matrix.ServerName,
|
||||||
UserAPI: userAPI,
|
UserAPI: userAPI,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ package producers
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -34,6 +35,7 @@ type SyncAPIProducer struct {
|
||||||
TopicSendToDeviceEvent string
|
TopicSendToDeviceEvent string
|
||||||
TopicTypingEvent string
|
TopicTypingEvent string
|
||||||
TopicPresenceEvent string
|
TopicPresenceEvent string
|
||||||
|
TopicDeviceListUpdate string
|
||||||
JetStream nats.JetStreamContext
|
JetStream nats.JetStreamContext
|
||||||
ServerName gomatrixserverlib.ServerName
|
ServerName gomatrixserverlib.ServerName
|
||||||
UserAPI userapi.UserInternalAPI
|
UserAPI userapi.UserInternalAPI
|
||||||
|
|
@ -157,7 +159,22 @@ func (p *SyncAPIProducer) SendPresence(
|
||||||
lastActiveTS := gomatrixserverlib.AsTimestamp(time.Now().Add(-(time.Duration(lastActiveAgo) * time.Millisecond)))
|
lastActiveTS := gomatrixserverlib.AsTimestamp(time.Now().Add(-(time.Duration(lastActiveAgo) * time.Millisecond)))
|
||||||
|
|
||||||
m.Header.Set("last_active_ts", strconv.Itoa(int(lastActiveTS)))
|
m.Header.Set("last_active_ts", strconv.Itoa(int(lastActiveTS)))
|
||||||
log.Debugf("Sending presence to syncAPI: %+v", m.Header)
|
log.Tracef("Sending presence to syncAPI: %+v", m.Header)
|
||||||
_, err := p.JetStream.PublishMsg(m, nats.Context(ctx))
|
_, err := p.JetStream.PublishMsg(m, nats.Context(ctx))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *SyncAPIProducer) SendDeviceListUpdate(
|
||||||
|
ctx context.Context, deviceListUpdate *gomatrixserverlib.DeviceListUpdateEvent,
|
||||||
|
) (err error) {
|
||||||
|
m := nats.NewMsg(p.TopicDeviceListUpdate)
|
||||||
|
m.Header.Set(jetstream.UserID, deviceListUpdate.UserID)
|
||||||
|
m.Data, err = json.Marshal(deviceListUpdate)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("json.Marshal: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Sending device list update: %+v", m.Header)
|
||||||
|
_, err = p.JetStream.PublishMsg(m, nats.Context(ctx))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,9 @@ func GetUserDevices(
|
||||||
if targetKey, ok := targetUser[gomatrixserverlib.KeyID(dev.DeviceID)]; ok {
|
if targetKey, ok := targetUser[gomatrixserverlib.KeyID(dev.DeviceID)]; ok {
|
||||||
for sourceUserID, forSourceUser := range targetKey {
|
for sourceUserID, forSourceUser := range targetKey {
|
||||||
for sourceKeyID, sourceKey := range forSourceUser {
|
for sourceKeyID, sourceKey := range forSourceUser {
|
||||||
|
if device.Keys.Signatures == nil {
|
||||||
|
device.Keys.Signatures = map[string]map[gomatrixserverlib.KeyID]gomatrixserverlib.Base64Bytes{}
|
||||||
|
}
|
||||||
if _, ok := device.Keys.Signatures[sourceUserID]; !ok {
|
if _, ok := device.Keys.Signatures[sourceUserID]; !ok {
|
||||||
device.Keys.Signatures[sourceUserID] = map[gomatrixserverlib.KeyID]gomatrixserverlib.Base64Bytes{}
|
device.Keys.Signatures[sourceUserID] = map[gomatrixserverlib.KeyID]gomatrixserverlib.Base64Bytes{}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -501,11 +501,7 @@ func (t *txnReq) processDeviceListUpdate(ctx context.Context, e gomatrixserverli
|
||||||
} else if serverName != t.Origin {
|
} else if serverName != t.Origin {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var inputRes keyapi.InputDeviceListUpdateResponse
|
if err := t.producer.SendDeviceListUpdate(ctx, &payload); err != nil {
|
||||||
t.keyAPI.InputDeviceListUpdate(context.Background(), &keyapi.InputDeviceListUpdateRequest{
|
util.GetLogger(ctx).WithError(err).WithField("user_id", payload.UserID).Error("failed to InputDeviceListUpdate")
|
||||||
Event: payload,
|
|
||||||
}, &inputRes)
|
|
||||||
if inputRes.Error != nil {
|
|
||||||
util.GetLogger(ctx).WithError(inputRes.Error).WithField("user_id", payload.UserID).Error("failed to InputDeviceListUpdate")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
go.mod
4
go.mod
|
|
@ -2,7 +2,7 @@ module github.com/matrix-org/dendrite
|
||||||
|
|
||||||
replace github.com/nats-io/nats-server/v2 => github.com/neilalexander/nats-server/v2 v2.8.3-0.20220513095553-73a9a246d34f
|
replace github.com/nats-io/nats-server/v2 => github.com/neilalexander/nats-server/v2 v2.8.3-0.20220513095553-73a9a246d34f
|
||||||
|
|
||||||
replace github.com/nats-io/nats.go => github.com/neilalexander/nats.go v1.13.1-0.20220419101051-b262d9f0be1e
|
replace github.com/nats-io/nats.go => github.com/neilalexander/nats.go v1.13.1-0.20220621084451-ac518c356673
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Arceliar/ironwood v0.0.0-20220306165321-319147a02d98
|
github.com/Arceliar/ironwood v0.0.0-20220306165321-319147a02d98
|
||||||
|
|
@ -35,7 +35,7 @@ require (
|
||||||
github.com/matrix-org/dugong v0.0.0-20210921133753-66e6b1c67e2e
|
github.com/matrix-org/dugong v0.0.0-20210921133753-66e6b1c67e2e
|
||||||
github.com/matrix-org/go-sqlite3-js v0.0.0-20220419092513-28aa791a1c91
|
github.com/matrix-org/go-sqlite3-js v0.0.0-20220419092513-28aa791a1c91
|
||||||
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16
|
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16
|
||||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20220530084946-3a4b148706bc
|
github.com/matrix-org/gomatrixserverlib v0.0.0-20220701090733-da53994b0c7f
|
||||||
github.com/matrix-org/pinecone v0.0.0-20220408153826-2999ea29ed48
|
github.com/matrix-org/pinecone v0.0.0-20220408153826-2999ea29ed48
|
||||||
github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4
|
github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4
|
||||||
github.com/mattn/go-sqlite3 v1.14.13
|
github.com/mattn/go-sqlite3 v1.14.13
|
||||||
|
|
|
||||||
8
go.sum
8
go.sum
|
|
@ -460,8 +460,8 @@ github.com/matrix-org/go-sqlite3-js v0.0.0-20220419092513-28aa791a1c91/go.mod h1
|
||||||
github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0=
|
github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0=
|
||||||
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 h1:ZtO5uywdd5dLDCud4r0r55eP4j9FuUNpl60Gmntcop4=
|
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 h1:ZtO5uywdd5dLDCud4r0r55eP4j9FuUNpl60Gmntcop4=
|
||||||
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s=
|
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s=
|
||||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20220530084946-3a4b148706bc h1:58tT3VznINdRWimb3yYb8QWmTAHX9AAuyOwzdmrp9q4=
|
github.com/matrix-org/gomatrixserverlib v0.0.0-20220701090733-da53994b0c7f h1:XF2+J6sOq07yhK1I7ItwsgRwXorjj7gqiCvgZ4dn8W8=
|
||||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20220530084946-3a4b148706bc/go.mod h1:jX38yp3SSLJNftBg3PXU1ayd0PCLIiDHQ4xAc9DIixk=
|
github.com/matrix-org/gomatrixserverlib v0.0.0-20220701090733-da53994b0c7f/go.mod h1:jX38yp3SSLJNftBg3PXU1ayd0PCLIiDHQ4xAc9DIixk=
|
||||||
github.com/matrix-org/pinecone v0.0.0-20220408153826-2999ea29ed48 h1:W0sjjC6yjskHX4mb0nk3p0fXAlbU5bAFUFeEtlrPASE=
|
github.com/matrix-org/pinecone v0.0.0-20220408153826-2999ea29ed48 h1:W0sjjC6yjskHX4mb0nk3p0fXAlbU5bAFUFeEtlrPASE=
|
||||||
github.com/matrix-org/pinecone v0.0.0-20220408153826-2999ea29ed48/go.mod h1:ulJzsVOTssIVp1j/m5eI//4VpAGDkMt5NrRuAVX7wpc=
|
github.com/matrix-org/pinecone v0.0.0-20220408153826-2999ea29ed48/go.mod h1:ulJzsVOTssIVp1j/m5eI//4VpAGDkMt5NrRuAVX7wpc=
|
||||||
github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7/go.mod h1:vVQlW/emklohkZnOPwD3LrZUBqdfsbiyO3p1lNV8F6U=
|
github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7/go.mod h1:vVQlW/emklohkZnOPwD3LrZUBqdfsbiyO3p1lNV8F6U=
|
||||||
|
|
@ -526,8 +526,8 @@ github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJE
|
||||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
github.com/neilalexander/nats-server/v2 v2.8.3-0.20220513095553-73a9a246d34f h1:Fc+TjdV1mOy0oISSzfoxNWdTqjg7tN/Vdgf+B2cwvdo=
|
github.com/neilalexander/nats-server/v2 v2.8.3-0.20220513095553-73a9a246d34f h1:Fc+TjdV1mOy0oISSzfoxNWdTqjg7tN/Vdgf+B2cwvdo=
|
||||||
github.com/neilalexander/nats-server/v2 v2.8.3-0.20220513095553-73a9a246d34f/go.mod h1:vIdpKz3OG+DCg4q/xVPdXHoztEyKDWRtykQ4N7hd7C4=
|
github.com/neilalexander/nats-server/v2 v2.8.3-0.20220513095553-73a9a246d34f/go.mod h1:vIdpKz3OG+DCg4q/xVPdXHoztEyKDWRtykQ4N7hd7C4=
|
||||||
github.com/neilalexander/nats.go v1.13.1-0.20220419101051-b262d9f0be1e h1:kNIzIzj2OvnlreA+sTJ12nWJzTP3OSLNKDL/Iq9mF6Y=
|
github.com/neilalexander/nats.go v1.13.1-0.20220621084451-ac518c356673 h1:TcKfa3Tf0qwUotv63PQVu2d1bBoLi2iEA4RHVMGDh5M=
|
||||||
github.com/neilalexander/nats.go v1.13.1-0.20220419101051-b262d9f0be1e/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
|
github.com/neilalexander/nats.go v1.13.1-0.20220621084451-ac518c356673/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
|
||||||
github.com/neilalexander/utp v0.1.1-0.20210727203401-54ae7b1cd5f9 h1:lrVQzBtkeQEGGYUHwSX1XPe1E5GL6U3KYCNe2G4bncQ=
|
github.com/neilalexander/utp v0.1.1-0.20210727203401-54ae7b1cd5f9 h1:lrVQzBtkeQEGGYUHwSX1XPe1E5GL6U3KYCNe2G4bncQ=
|
||||||
github.com/neilalexander/utp v0.1.1-0.20210727203401-54ae7b1cd5f9/go.mod h1:NPHGhPc0/wudcaCqL/H5AOddkRf8GPRhzOujuUKGQu8=
|
github.com/neilalexander/utp v0.1.1-0.20210727203401-54ae7b1cd5f9/go.mod h1:NPHGhPc0/wudcaCqL/H5AOddkRf8GPRhzOujuUKGQu8=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -17,6 +18,7 @@ type RateLimits struct {
|
||||||
enabled bool
|
enabled bool
|
||||||
requestThreshold int64
|
requestThreshold int64
|
||||||
cooloffDuration time.Duration
|
cooloffDuration time.Duration
|
||||||
|
exemptUserIDs map[string]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRateLimits(cfg *config.RateLimiting) *RateLimits {
|
func NewRateLimits(cfg *config.RateLimiting) *RateLimits {
|
||||||
|
|
@ -25,6 +27,10 @@ func NewRateLimits(cfg *config.RateLimiting) *RateLimits {
|
||||||
enabled: cfg.Enabled,
|
enabled: cfg.Enabled,
|
||||||
requestThreshold: cfg.Threshold,
|
requestThreshold: cfg.Threshold,
|
||||||
cooloffDuration: time.Duration(cfg.CooloffMS) * time.Millisecond,
|
cooloffDuration: time.Duration(cfg.CooloffMS) * time.Millisecond,
|
||||||
|
exemptUserIDs: map[string]struct{}{},
|
||||||
|
}
|
||||||
|
for _, userID := range cfg.ExemptUserIDs {
|
||||||
|
l.exemptUserIDs[userID] = struct{}{}
|
||||||
}
|
}
|
||||||
if l.enabled {
|
if l.enabled {
|
||||||
go l.clean()
|
go l.clean()
|
||||||
|
|
@ -52,7 +58,7 @@ func (l *RateLimits) clean() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *RateLimits) Limit(req *http.Request) *util.JSONResponse {
|
func (l *RateLimits) Limit(req *http.Request, device *userapi.Device) *util.JSONResponse {
|
||||||
// If rate limiting is disabled then do nothing.
|
// If rate limiting is disabled then do nothing.
|
||||||
if !l.enabled {
|
if !l.enabled {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -67,9 +73,26 @@ func (l *RateLimits) Limit(req *http.Request) *util.JSONResponse {
|
||||||
|
|
||||||
// First of all, work out if X-Forwarded-For was sent to us. If not
|
// First of all, work out if X-Forwarded-For was sent to us. If not
|
||||||
// then we'll just use the IP address of the caller.
|
// then we'll just use the IP address of the caller.
|
||||||
caller := req.RemoteAddr
|
var caller string
|
||||||
if forwardedFor := req.Header.Get("X-Forwarded-For"); forwardedFor != "" {
|
if device != nil {
|
||||||
caller = forwardedFor
|
switch device.AccountType {
|
||||||
|
case userapi.AccountTypeAdmin:
|
||||||
|
return nil // don't rate-limit server administrators
|
||||||
|
case userapi.AccountTypeAppService:
|
||||||
|
return nil // don't rate-limit appservice users
|
||||||
|
default:
|
||||||
|
if _, ok := l.exemptUserIDs[device.UserID]; ok {
|
||||||
|
// If the user is exempt from rate limiting then do nothing.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
caller = device.UserID + device.ID
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if forwardedFor := req.Header.Get("X-Forwarded-For"); forwardedFor != "" {
|
||||||
|
caller = forwardedFor
|
||||||
|
} else {
|
||||||
|
caller = req.RemoteAddr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up the caller's channel, if they have one.
|
// Look up the caller's channel, if they have one.
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ var build string
|
||||||
const (
|
const (
|
||||||
VersionMajor = 0
|
VersionMajor = 0
|
||||||
VersionMinor = 8
|
VersionMinor = 8
|
||||||
VersionPatch = 6
|
VersionPatch = 9
|
||||||
VersionTag = "" // example: "rc1"
|
VersionTag = "" // example: "rc1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,8 +62,6 @@ type FederationKeyAPI interface {
|
||||||
QueryKeys(ctx context.Context, req *QueryKeysRequest, res *QueryKeysResponse)
|
QueryKeys(ctx context.Context, req *QueryKeysRequest, res *QueryKeysResponse)
|
||||||
QuerySignatures(ctx context.Context, req *QuerySignaturesRequest, res *QuerySignaturesResponse)
|
QuerySignatures(ctx context.Context, req *QuerySignaturesRequest, res *QuerySignaturesResponse)
|
||||||
QueryDeviceMessages(ctx context.Context, req *QueryDeviceMessagesRequest, res *QueryDeviceMessagesResponse)
|
QueryDeviceMessages(ctx context.Context, req *QueryDeviceMessagesRequest, res *QueryDeviceMessagesResponse)
|
||||||
// InputDeviceListUpdate from a federated server EDU
|
|
||||||
InputDeviceListUpdate(ctx context.Context, req *InputDeviceListUpdateRequest, res *InputDeviceListUpdateResponse)
|
|
||||||
PerformUploadDeviceKeys(ctx context.Context, req *PerformUploadDeviceKeysRequest, res *PerformUploadDeviceKeysResponse)
|
PerformUploadDeviceKeys(ctx context.Context, req *PerformUploadDeviceKeysRequest, res *PerformUploadDeviceKeysResponse)
|
||||||
PerformClaimKeys(ctx context.Context, req *PerformClaimKeysRequest, res *PerformClaimKeysResponse)
|
PerformClaimKeys(ctx context.Context, req *PerformClaimKeysRequest, res *PerformClaimKeysResponse)
|
||||||
}
|
}
|
||||||
|
|
@ -337,11 +335,3 @@ type QuerySignaturesResponse struct {
|
||||||
// The request error, if any
|
// The request error, if any
|
||||||
Error *KeyError
|
Error *KeyError
|
||||||
}
|
}
|
||||||
|
|
||||||
type InputDeviceListUpdateRequest struct {
|
|
||||||
Event gomatrixserverlib.DeviceListUpdateEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
type InputDeviceListUpdateResponse struct {
|
|
||||||
Error *KeyError
|
|
||||||
}
|
|
||||||
|
|
|
||||||
82
keyserver/consumers/devicelistupdate.go
Normal file
82
keyserver/consumers/devicelistupdate.go
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
// 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 consumers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/keyserver/internal"
|
||||||
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
|
"github.com/matrix-org/dendrite/setup/process"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/nats-io/nats.go"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeviceListUpdateConsumer consumes device list updates that came in over federation.
|
||||||
|
type DeviceListUpdateConsumer struct {
|
||||||
|
ctx context.Context
|
||||||
|
jetstream nats.JetStreamContext
|
||||||
|
durable string
|
||||||
|
topic string
|
||||||
|
updater *internal.DeviceListUpdater
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDeviceListUpdateConsumer creates a new DeviceListConsumer. Call Start() to begin consuming from key servers.
|
||||||
|
func NewDeviceListUpdateConsumer(
|
||||||
|
process *process.ProcessContext,
|
||||||
|
cfg *config.KeyServer,
|
||||||
|
js nats.JetStreamContext,
|
||||||
|
updater *internal.DeviceListUpdater,
|
||||||
|
) *DeviceListUpdateConsumer {
|
||||||
|
return &DeviceListUpdateConsumer{
|
||||||
|
ctx: process.Context(),
|
||||||
|
jetstream: js,
|
||||||
|
durable: cfg.Matrix.JetStream.Prefixed("KeyServerInputDeviceListConsumer"),
|
||||||
|
topic: cfg.Matrix.JetStream.Prefixed(jetstream.InputDeviceListUpdate),
|
||||||
|
updater: updater,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start consuming from key servers
|
||||||
|
func (t *DeviceListUpdateConsumer) Start() error {
|
||||||
|
return jetstream.JetStreamConsumer(
|
||||||
|
t.ctx, t.jetstream, t.topic, t.durable, t.onMessage,
|
||||||
|
nats.DeliverAll(), nats.ManualAck(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// onMessage is called in response to a message received on the
|
||||||
|
// key change events topic from the key server.
|
||||||
|
func (t *DeviceListUpdateConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||||
|
var m gomatrixserverlib.DeviceListUpdateEvent
|
||||||
|
if err := json.Unmarshal(msg.Data, &m); err != nil {
|
||||||
|
logrus.WithError(err).Errorf("Failed to read from device list update input topic")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
err := t.updater.Update(ctx, m)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"user_id": m.UserID,
|
||||||
|
"device_id": m.DeviceID,
|
||||||
|
"stream_id": m.StreamID,
|
||||||
|
"prev_id": m.PrevID,
|
||||||
|
}).WithError(err).Errorf("Failed to update device list")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
@ -374,7 +374,7 @@ func (u *DeviceListUpdater) processServer(serverName gomatrixserverlib.ServerNam
|
||||||
// fetch stale device lists
|
// fetch stale device lists
|
||||||
userIDs, err := u.db.StaleDeviceLists(ctx, []gomatrixserverlib.ServerName{serverName})
|
userIDs, err := u.db.StaleDeviceLists(ctx, []gomatrixserverlib.ServerName{serverName})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithError(err).Error("failed to load stale device lists")
|
logger.WithError(err).Error("Failed to load stale device lists")
|
||||||
return waitTime, true
|
return waitTime, true
|
||||||
}
|
}
|
||||||
failCount := 0
|
failCount := 0
|
||||||
|
|
@ -399,7 +399,7 @@ func (u *DeviceListUpdater) processServer(serverName gomatrixserverlib.ServerNam
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
waitTime = time.Hour
|
waitTime = time.Hour
|
||||||
logger.WithError(err).WithField("user_id", userID).Warn("GetUserDevices returned unknown error type")
|
logger.WithError(err).WithField("user_id", userID).Debug("GetUserDevices returned unknown error type")
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -422,12 +422,12 @@ func (u *DeviceListUpdater) processServer(serverName gomatrixserverlib.ServerNam
|
||||||
}
|
}
|
||||||
err = u.updateDeviceList(&res)
|
err = u.updateDeviceList(&res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithError(err).WithField("user_id", userID).Error("fetched device list but failed to store/emit it")
|
logger.WithError(err).WithField("user_id", userID).Error("Fetched device list but failed to store/emit it")
|
||||||
failCount += 1
|
failCount += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if failCount > 0 {
|
if failCount > 0 {
|
||||||
logger.WithField("total", len(userIDs)).WithField("failed", failCount).WithField("wait", waitTime).Error("failed to query device keys for some users")
|
logger.WithField("total", len(userIDs)).WithField("failed", failCount).WithField("wait", waitTime).Warn("Failed to query device keys for some users")
|
||||||
}
|
}
|
||||||
for _, userID := range userIDs {
|
for _, userID := range userIDs {
|
||||||
// always clear the channel to unblock Update calls regardless of success/failure
|
// always clear the channel to unblock Update calls regardless of success/failure
|
||||||
|
|
|
||||||
|
|
@ -47,17 +47,6 @@ func (a *KeyInternalAPI) SetUserAPI(i userapi.KeyserverUserAPI) {
|
||||||
a.UserAPI = i
|
a.UserAPI = i
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *KeyInternalAPI) InputDeviceListUpdate(
|
|
||||||
ctx context.Context, req *api.InputDeviceListUpdateRequest, res *api.InputDeviceListUpdateResponse,
|
|
||||||
) {
|
|
||||||
err := a.Updater.Update(ctx, req.Event)
|
|
||||||
if err != nil {
|
|
||||||
res.Error = &api.KeyError{
|
|
||||||
Err: fmt.Sprintf("failed to update device list: %s", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *KeyInternalAPI) QueryKeyChanges(ctx context.Context, req *api.QueryKeyChangesRequest, res *api.QueryKeyChangesResponse) {
|
func (a *KeyInternalAPI) QueryKeyChanges(ctx context.Context, req *api.QueryKeyChangesRequest, res *api.QueryKeyChangesResponse) {
|
||||||
userIDs, latest, err := a.DB.KeyChanges(ctx, req.Offset, req.ToOffset)
|
userIDs, latest, err := a.DB.KeyChanges(ctx, req.Offset, req.ToOffset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -63,20 +63,6 @@ type httpKeyInternalAPI struct {
|
||||||
func (h *httpKeyInternalAPI) SetUserAPI(i userapi.KeyserverUserAPI) {
|
func (h *httpKeyInternalAPI) SetUserAPI(i userapi.KeyserverUserAPI) {
|
||||||
// no-op: doesn't need it
|
// no-op: doesn't need it
|
||||||
}
|
}
|
||||||
func (h *httpKeyInternalAPI) InputDeviceListUpdate(
|
|
||||||
ctx context.Context, req *api.InputDeviceListUpdateRequest, res *api.InputDeviceListUpdateResponse,
|
|
||||||
) {
|
|
||||||
span, ctx := opentracing.StartSpanFromContext(ctx, "InputDeviceListUpdate")
|
|
||||||
defer span.Finish()
|
|
||||||
|
|
||||||
apiURL := h.apiURL + InputDeviceListUpdatePath
|
|
||||||
err := httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res)
|
|
||||||
if err != nil {
|
|
||||||
res.Error = &api.KeyError{
|
|
||||||
Err: err.Error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *httpKeyInternalAPI) PerformClaimKeys(
|
func (h *httpKeyInternalAPI) PerformClaimKeys(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
|
|
||||||
|
|
@ -25,17 +25,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func AddRoutes(internalAPIMux *mux.Router, s api.KeyInternalAPI) {
|
func AddRoutes(internalAPIMux *mux.Router, s api.KeyInternalAPI) {
|
||||||
internalAPIMux.Handle(InputDeviceListUpdatePath,
|
|
||||||
httputil.MakeInternalAPI("inputDeviceListUpdate", func(req *http.Request) util.JSONResponse {
|
|
||||||
request := api.InputDeviceListUpdateRequest{}
|
|
||||||
response := api.InputDeviceListUpdateResponse{}
|
|
||||||
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
|
|
||||||
return util.MessageResponse(http.StatusBadRequest, err.Error())
|
|
||||||
}
|
|
||||||
s.InputDeviceListUpdate(req.Context(), &request, &response)
|
|
||||||
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
internalAPIMux.Handle(PerformClaimKeysPath,
|
internalAPIMux.Handle(PerformClaimKeysPath,
|
||||||
httputil.MakeInternalAPI("performClaimKeys", func(req *http.Request) util.JSONResponse {
|
httputil.MakeInternalAPI("performClaimKeys", func(req *http.Request) util.JSONResponse {
|
||||||
request := api.PerformClaimKeysRequest{}
|
request := api.PerformClaimKeysRequest{}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
fedsenderapi "github.com/matrix-org/dendrite/federationapi/api"
|
fedsenderapi "github.com/matrix-org/dendrite/federationapi/api"
|
||||||
"github.com/matrix-org/dendrite/keyserver/api"
|
"github.com/matrix-org/dendrite/keyserver/api"
|
||||||
|
"github.com/matrix-org/dendrite/keyserver/consumers"
|
||||||
"github.com/matrix-org/dendrite/keyserver/internal"
|
"github.com/matrix-org/dendrite/keyserver/internal"
|
||||||
"github.com/matrix-org/dendrite/keyserver/inthttp"
|
"github.com/matrix-org/dendrite/keyserver/inthttp"
|
||||||
"github.com/matrix-org/dendrite/keyserver/producers"
|
"github.com/matrix-org/dendrite/keyserver/producers"
|
||||||
|
|
@ -59,10 +60,17 @@ func NewInternalAPI(
|
||||||
updater := internal.NewDeviceListUpdater(db, ap, keyChangeProducer, fedClient, 8) // 8 workers TODO: configurable
|
updater := internal.NewDeviceListUpdater(db, ap, keyChangeProducer, fedClient, 8) // 8 workers TODO: configurable
|
||||||
ap.Updater = updater
|
ap.Updater = updater
|
||||||
go func() {
|
go func() {
|
||||||
if err := updater.Start(); err != nil {
|
if err = updater.Start(); err != nil {
|
||||||
logrus.WithError(err).Panicf("failed to start device list updater")
|
logrus.WithError(err).Panicf("failed to start device list updater")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
dlConsumer := consumers.NewDeviceListUpdateConsumer(
|
||||||
|
base.ProcessContext, cfg, js, updater,
|
||||||
|
)
|
||||||
|
if err = dlConsumer.Start(); err != nil {
|
||||||
|
logrus.WithError(err).Panic("failed to start device list consumer")
|
||||||
|
}
|
||||||
|
|
||||||
return ap
|
return ap
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ func Setup(
|
||||||
uploadHandler := httputil.MakeAuthAPI(
|
uploadHandler := httputil.MakeAuthAPI(
|
||||||
"upload", userAPI,
|
"upload", userAPI,
|
||||||
func(req *http.Request, dev *userapi.Device) util.JSONResponse {
|
func(req *http.Request, dev *userapi.Device) util.JSONResponse {
|
||||||
if r := rateLimits.Limit(req); r != nil {
|
if r := rateLimits.Limit(req, dev); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
return Upload(req, cfg, dev, db, activeThumbnailGeneration)
|
return Upload(req, cfg, dev, db, activeThumbnailGeneration)
|
||||||
|
|
@ -70,7 +70,7 @@ func Setup(
|
||||||
)
|
)
|
||||||
|
|
||||||
configHandler := httputil.MakeAuthAPI("config", userAPI, 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 {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
respondSize := &cfg.MaxFileSizeBytes
|
respondSize := &cfg.MaxFileSizeBytes
|
||||||
|
|
@ -126,7 +126,7 @@ func makeDownloadAPI(
|
||||||
// Ratelimit requests
|
// Ratelimit requests
|
||||||
// NOTSPEC: The spec says everything at /media/ should be rate limited, but this causes issues with thumbnails (#2243)
|
// NOTSPEC: The spec says everything at /media/ should be rate limited, but this causes issues with thumbnails (#2243)
|
||||||
if name != "thumbnail" {
|
if name != "thumbnail" {
|
||||||
if r := rateLimits.Limit(req); r != nil {
|
if r := rateLimits.Limit(req, nil); r != nil {
|
||||||
if err := json.NewEncoder(w).Encode(r); err != nil {
|
if err := json.NewEncoder(w).Encode(r); err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -140,11 +140,8 @@ type ClientRoomserverAPI interface {
|
||||||
|
|
||||||
// PerformRoomUpgrade upgrades a room to a newer version
|
// PerformRoomUpgrade upgrades a room to a newer version
|
||||||
PerformRoomUpgrade(ctx context.Context, req *PerformRoomUpgradeRequest, resp *PerformRoomUpgradeResponse)
|
PerformRoomUpgrade(ctx context.Context, req *PerformRoomUpgradeRequest, resp *PerformRoomUpgradeResponse)
|
||||||
PerformAdminEvacuateRoom(
|
PerformAdminEvacuateRoom(ctx context.Context, req *PerformAdminEvacuateRoomRequest, res *PerformAdminEvacuateRoomResponse)
|
||||||
ctx context.Context,
|
PerformAdminEvacuateUser(ctx context.Context, req *PerformAdminEvacuateUserRequest, res *PerformAdminEvacuateUserResponse)
|
||||||
req *PerformAdminEvacuateRoomRequest,
|
|
||||||
res *PerformAdminEvacuateRoomResponse,
|
|
||||||
)
|
|
||||||
PerformPeek(ctx context.Context, req *PerformPeekRequest, res *PerformPeekResponse)
|
PerformPeek(ctx context.Context, req *PerformPeekRequest, res *PerformPeekResponse)
|
||||||
PerformUnpeek(ctx context.Context, req *PerformUnpeekRequest, res *PerformUnpeekResponse)
|
PerformUnpeek(ctx context.Context, req *PerformUnpeekRequest, res *PerformUnpeekResponse)
|
||||||
PerformInvite(ctx context.Context, req *PerformInviteRequest, res *PerformInviteResponse) error
|
PerformInvite(ctx context.Context, req *PerformInviteRequest, res *PerformInviteResponse) error
|
||||||
|
|
@ -161,6 +158,7 @@ type UserRoomserverAPI interface {
|
||||||
QueryLatestEventsAndStateAPI
|
QueryLatestEventsAndStateAPI
|
||||||
QueryCurrentState(ctx context.Context, req *QueryCurrentStateRequest, res *QueryCurrentStateResponse) error
|
QueryCurrentState(ctx context.Context, req *QueryCurrentStateRequest, res *QueryCurrentStateResponse) error
|
||||||
QueryMembershipsForRoom(ctx context.Context, req *QueryMembershipsForRoomRequest, res *QueryMembershipsForRoomResponse) error
|
QueryMembershipsForRoom(ctx context.Context, req *QueryMembershipsForRoomRequest, res *QueryMembershipsForRoomResponse) error
|
||||||
|
PerformAdminEvacuateUser(ctx context.Context, req *PerformAdminEvacuateUserRequest, res *PerformAdminEvacuateUserResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
type FederationRoomserverAPI interface {
|
type FederationRoomserverAPI interface {
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,15 @@ func (t *RoomserverInternalAPITrace) PerformAdminEvacuateRoom(
|
||||||
util.GetLogger(ctx).Infof("PerformAdminEvacuateRoom req=%+v res=%+v", js(req), js(res))
|
util.GetLogger(ctx).Infof("PerformAdminEvacuateRoom req=%+v res=%+v", js(req), js(res))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *RoomserverInternalAPITrace) PerformAdminEvacuateUser(
|
||||||
|
ctx context.Context,
|
||||||
|
req *PerformAdminEvacuateUserRequest,
|
||||||
|
res *PerformAdminEvacuateUserResponse,
|
||||||
|
) {
|
||||||
|
t.Impl.PerformAdminEvacuateUser(ctx, req, res)
|
||||||
|
util.GetLogger(ctx).Infof("PerformAdminEvacuateUser req=%+v res=%+v", js(req), js(res))
|
||||||
|
}
|
||||||
|
|
||||||
func (t *RoomserverInternalAPITrace) PerformInboundPeek(
|
func (t *RoomserverInternalAPITrace) PerformInboundPeek(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *PerformInboundPeekRequest,
|
req *PerformInboundPeekRequest,
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,8 @@ type OutputNewRoomEvent struct {
|
||||||
// The transaction ID of the send request if sent by a local user and one
|
// The transaction ID of the send request if sent by a local user and one
|
||||||
// was specified
|
// was specified
|
||||||
TransactionID *TransactionID `json:"transaction_id,omitempty"`
|
TransactionID *TransactionID `json:"transaction_id,omitempty"`
|
||||||
|
// The history visibility of the event.
|
||||||
|
HistoryVisibility gomatrixserverlib.HistoryVisibility `json:"history_visibility"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OutputNewRoomEvent) NeededStateEventIDs() ([]*gomatrixserverlib.HeaderedEvent, []string) {
|
func (o *OutputNewRoomEvent) NeededStateEventIDs() ([]*gomatrixserverlib.HeaderedEvent, []string) {
|
||||||
|
|
@ -187,7 +189,8 @@ func (o *OutputNewRoomEvent) NeededStateEventIDs() ([]*gomatrixserverlib.Headere
|
||||||
// should build their current room state up from OutputNewRoomEvents only.
|
// should build their current room state up from OutputNewRoomEvents only.
|
||||||
type OutputOldRoomEvent struct {
|
type OutputOldRoomEvent struct {
|
||||||
// The Event.
|
// The Event.
|
||||||
Event *gomatrixserverlib.HeaderedEvent `json:"event"`
|
Event *gomatrixserverlib.HeaderedEvent `json:"event"`
|
||||||
|
HistoryVisibility gomatrixserverlib.HistoryVisibility `json:"history_visibility"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// An OutputNewInviteEvent is written whenever an invite becomes active.
|
// An OutputNewInviteEvent is written whenever an invite becomes active.
|
||||||
|
|
|
||||||
|
|
@ -223,3 +223,12 @@ type PerformAdminEvacuateRoomResponse struct {
|
||||||
Affected []string `json:"affected"`
|
Affected []string `json:"affected"`
|
||||||
Error *PerformError
|
Error *PerformError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PerformAdminEvacuateUserRequest struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PerformAdminEvacuateUserResponse struct {
|
||||||
|
Affected []string `json:"affected"`
|
||||||
|
Error *PerformError
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -216,11 +216,10 @@ func (r *RoomserverInternalAPI) RemoveRoomAlias(
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = api.SendEvents(ctx, r.RSAPI, api.KindNew, []*gomatrixserverlib.HeaderedEvent{newEvent}, r.ServerName, r.ServerName, nil, false)
|
err = api.SendEvents(ctx, r, api.KindNew, []*gomatrixserverlib.HeaderedEvent{newEvent}, r.ServerName, r.ServerName, nil, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,10 @@ import (
|
||||||
"github.com/matrix-org/dendrite/roomserver/internal/input"
|
"github.com/matrix-org/dendrite/roomserver/internal/input"
|
||||||
"github.com/matrix-org/dendrite/roomserver/internal/perform"
|
"github.com/matrix-org/dendrite/roomserver/internal/perform"
|
||||||
"github.com/matrix-org/dendrite/roomserver/internal/query"
|
"github.com/matrix-org/dendrite/roomserver/internal/query"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/producers"
|
||||||
"github.com/matrix-org/dendrite/roomserver/storage"
|
"github.com/matrix-org/dendrite/roomserver/storage"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
"github.com/matrix-org/dendrite/setup/process"
|
"github.com/matrix-org/dendrite/setup/process"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
@ -49,17 +51,21 @@ type RoomserverInternalAPI struct {
|
||||||
JetStream nats.JetStreamContext
|
JetStream nats.JetStreamContext
|
||||||
Durable string
|
Durable string
|
||||||
InputRoomEventTopic string // JetStream topic for new input room events
|
InputRoomEventTopic string // JetStream topic for new input room events
|
||||||
OutputRoomEventTopic string // JetStream topic for new output room events
|
OutputProducer *producers.RoomEventProducer
|
||||||
PerspectiveServerNames []gomatrixserverlib.ServerName
|
PerspectiveServerNames []gomatrixserverlib.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRoomserverAPI(
|
func NewRoomserverAPI(
|
||||||
processCtx *process.ProcessContext, cfg *config.RoomServer, roomserverDB storage.Database,
|
processCtx *process.ProcessContext, cfg *config.RoomServer, roomserverDB storage.Database,
|
||||||
consumer nats.JetStreamContext, nc *nats.Conn,
|
js nats.JetStreamContext, nc *nats.Conn, inputRoomEventTopic string,
|
||||||
inputRoomEventTopic, outputRoomEventTopic string,
|
|
||||||
caches caching.RoomServerCaches, perspectiveServerNames []gomatrixserverlib.ServerName,
|
caches caching.RoomServerCaches, perspectiveServerNames []gomatrixserverlib.ServerName,
|
||||||
) *RoomserverInternalAPI {
|
) *RoomserverInternalAPI {
|
||||||
serverACLs := acls.NewServerACLs(roomserverDB)
|
serverACLs := acls.NewServerACLs(roomserverDB)
|
||||||
|
producer := &producers.RoomEventProducer{
|
||||||
|
Topic: string(cfg.Matrix.JetStream.Prefixed(jetstream.OutputRoomEvent)),
|
||||||
|
JetStream: js,
|
||||||
|
ACLs: serverACLs,
|
||||||
|
}
|
||||||
a := &RoomserverInternalAPI{
|
a := &RoomserverInternalAPI{
|
||||||
ProcessContext: processCtx,
|
ProcessContext: processCtx,
|
||||||
DB: roomserverDB,
|
DB: roomserverDB,
|
||||||
|
|
@ -68,8 +74,8 @@ func NewRoomserverAPI(
|
||||||
ServerName: cfg.Matrix.ServerName,
|
ServerName: cfg.Matrix.ServerName,
|
||||||
PerspectiveServerNames: perspectiveServerNames,
|
PerspectiveServerNames: perspectiveServerNames,
|
||||||
InputRoomEventTopic: inputRoomEventTopic,
|
InputRoomEventTopic: inputRoomEventTopic,
|
||||||
OutputRoomEventTopic: outputRoomEventTopic,
|
OutputProducer: producer,
|
||||||
JetStream: consumer,
|
JetStream: js,
|
||||||
NATSClient: nc,
|
NATSClient: nc,
|
||||||
Durable: cfg.Matrix.JetStream.Durable("RoomserverInputConsumer"),
|
Durable: cfg.Matrix.JetStream.Durable("RoomserverInputConsumer"),
|
||||||
ServerACLs: serverACLs,
|
ServerACLs: serverACLs,
|
||||||
|
|
@ -92,19 +98,19 @@ func (r *RoomserverInternalAPI) SetFederationAPI(fsAPI fsAPI.RoomserverFederatio
|
||||||
r.KeyRing = keyRing
|
r.KeyRing = keyRing
|
||||||
|
|
||||||
r.Inputer = &input.Inputer{
|
r.Inputer = &input.Inputer{
|
||||||
Cfg: r.Cfg,
|
Cfg: r.Cfg,
|
||||||
ProcessContext: r.ProcessContext,
|
ProcessContext: r.ProcessContext,
|
||||||
DB: r.DB,
|
DB: r.DB,
|
||||||
InputRoomEventTopic: r.InputRoomEventTopic,
|
InputRoomEventTopic: r.InputRoomEventTopic,
|
||||||
OutputRoomEventTopic: r.OutputRoomEventTopic,
|
OutputProducer: r.OutputProducer,
|
||||||
JetStream: r.JetStream,
|
JetStream: r.JetStream,
|
||||||
NATSClient: r.NATSClient,
|
NATSClient: r.NATSClient,
|
||||||
Durable: nats.Durable(r.Durable),
|
Durable: nats.Durable(r.Durable),
|
||||||
ServerName: r.Cfg.Matrix.ServerName,
|
ServerName: r.Cfg.Matrix.ServerName,
|
||||||
FSAPI: fsAPI,
|
FSAPI: fsAPI,
|
||||||
KeyRing: keyRing,
|
KeyRing: keyRing,
|
||||||
ACLs: r.ServerACLs,
|
ACLs: r.ServerACLs,
|
||||||
Queryer: r.Queryer,
|
Queryer: r.Queryer,
|
||||||
}
|
}
|
||||||
r.Inviter = &perform.Inviter{
|
r.Inviter = &perform.Inviter{
|
||||||
DB: r.DB,
|
DB: r.DB,
|
||||||
|
|
@ -170,6 +176,7 @@ func (r *RoomserverInternalAPI) SetFederationAPI(fsAPI fsAPI.RoomserverFederatio
|
||||||
Cfg: r.Cfg,
|
Cfg: r.Cfg,
|
||||||
Inputer: r.Inputer,
|
Inputer: r.Inputer,
|
||||||
Queryer: r.Queryer,
|
Queryer: r.Queryer,
|
||||||
|
Leaver: r.Leaver,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.Inputer.Start(); err != nil {
|
if err := r.Inputer.Start(); err != nil {
|
||||||
|
|
@ -198,7 +205,7 @@ func (r *RoomserverInternalAPI) PerformInvite(
|
||||||
if len(outputEvents) == 0 {
|
if len(outputEvents) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return r.WriteOutputEvents(req.Event.RoomID(), outputEvents)
|
return r.OutputProducer.ProduceRoomEvents(req.Event.RoomID(), outputEvents)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RoomserverInternalAPI) PerformLeave(
|
func (r *RoomserverInternalAPI) PerformLeave(
|
||||||
|
|
@ -214,7 +221,7 @@ func (r *RoomserverInternalAPI) PerformLeave(
|
||||||
if len(outputEvents) == 0 {
|
if len(outputEvents) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return r.WriteOutputEvents(req.RoomID, outputEvents)
|
return r.OutputProducer.ProduceRoomEvents(req.RoomID, outputEvents)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RoomserverInternalAPI) PerformForget(
|
func (r *RoomserverInternalAPI) PerformForget(
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/roomserver/acls"
|
"github.com/matrix-org/dendrite/roomserver/acls"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/roomserver/internal/query"
|
"github.com/matrix-org/dendrite/roomserver/internal/query"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/producers"
|
||||||
"github.com/matrix-org/dendrite/roomserver/storage"
|
"github.com/matrix-org/dendrite/roomserver/storage"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
|
|
@ -37,16 +38,8 @@ import (
|
||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/tidwall/gjson"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var keyContentFields = map[string]string{
|
|
||||||
"m.room.join_rules": "join_rule",
|
|
||||||
"m.room.history_visibility": "history_visibility",
|
|
||||||
"m.room.member": "membership",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inputer is responsible for consuming from the roomserver input
|
// Inputer is responsible for consuming from the roomserver input
|
||||||
// streams and processing the events. All input events are queued
|
// streams and processing the events. All input events are queued
|
||||||
// into a single NATS stream and the order is preserved strictly.
|
// into a single NATS stream and the order is preserved strictly.
|
||||||
|
|
@ -75,19 +68,19 @@ var keyContentFields = map[string]string{
|
||||||
// up, so they will do nothing until a new event comes in for B
|
// up, so they will do nothing until a new event comes in for B
|
||||||
// or C.
|
// or C.
|
||||||
type Inputer struct {
|
type Inputer struct {
|
||||||
Cfg *config.RoomServer
|
Cfg *config.RoomServer
|
||||||
ProcessContext *process.ProcessContext
|
ProcessContext *process.ProcessContext
|
||||||
DB storage.Database
|
DB storage.Database
|
||||||
NATSClient *nats.Conn
|
NATSClient *nats.Conn
|
||||||
JetStream nats.JetStreamContext
|
JetStream nats.JetStreamContext
|
||||||
Durable nats.SubOpt
|
Durable nats.SubOpt
|
||||||
ServerName gomatrixserverlib.ServerName
|
ServerName gomatrixserverlib.ServerName
|
||||||
FSAPI fedapi.RoomserverFederationAPI
|
FSAPI fedapi.RoomserverFederationAPI
|
||||||
KeyRing gomatrixserverlib.JSONVerifier
|
KeyRing gomatrixserverlib.JSONVerifier
|
||||||
ACLs *acls.ServerACLs
|
ACLs *acls.ServerACLs
|
||||||
InputRoomEventTopic string
|
InputRoomEventTopic string
|
||||||
OutputRoomEventTopic string
|
OutputProducer *producers.RoomEventProducer
|
||||||
workers sync.Map // room ID -> *worker
|
workers sync.Map // room ID -> *worker
|
||||||
|
|
||||||
Queryer *query.Queryer
|
Queryer *query.Queryer
|
||||||
}
|
}
|
||||||
|
|
@ -370,58 +363,6 @@ func (r *Inputer) InputRoomEvents(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteOutputEvents implements OutputRoomEventWriter
|
|
||||||
func (r *Inputer) WriteOutputEvents(roomID string, updates []api.OutputEvent) error {
|
|
||||||
var err error
|
|
||||||
for _, update := range updates {
|
|
||||||
msg := &nats.Msg{
|
|
||||||
Subject: r.OutputRoomEventTopic,
|
|
||||||
Header: nats.Header{},
|
|
||||||
}
|
|
||||||
msg.Header.Set(jetstream.RoomID, roomID)
|
|
||||||
msg.Data, err = json.Marshal(update)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger := log.WithFields(log.Fields{
|
|
||||||
"room_id": roomID,
|
|
||||||
"type": update.Type,
|
|
||||||
})
|
|
||||||
if update.NewRoomEvent != nil {
|
|
||||||
eventType := update.NewRoomEvent.Event.Type()
|
|
||||||
logger = logger.WithFields(log.Fields{
|
|
||||||
"event_type": eventType,
|
|
||||||
"event_id": update.NewRoomEvent.Event.EventID(),
|
|
||||||
"adds_state": len(update.NewRoomEvent.AddsStateEventIDs),
|
|
||||||
"removes_state": len(update.NewRoomEvent.RemovesStateEventIDs),
|
|
||||||
"send_as_server": update.NewRoomEvent.SendAsServer,
|
|
||||||
"sender": update.NewRoomEvent.Event.Sender(),
|
|
||||||
})
|
|
||||||
if update.NewRoomEvent.Event.StateKey() != nil {
|
|
||||||
logger = logger.WithField("state_key", *update.NewRoomEvent.Event.StateKey())
|
|
||||||
}
|
|
||||||
contentKey := keyContentFields[eventType]
|
|
||||||
if contentKey != "" {
|
|
||||||
value := gjson.GetBytes(update.NewRoomEvent.Event.Content(), contentKey)
|
|
||||||
if value.Exists() {
|
|
||||||
logger = logger.WithField("content_value", value.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if eventType == "m.room.server_acl" && update.NewRoomEvent.Event.StateKeyEquals("") {
|
|
||||||
ev := update.NewRoomEvent.Event.Unwrap()
|
|
||||||
defer r.ACLs.OnServerACLUpdate(ev)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.Tracef("Producing to topic '%s'", r.OutputRoomEventTopic)
|
|
||||||
if _, err := r.JetStream.PublishMsg(msg); err != nil {
|
|
||||||
logger.WithError(err).Errorf("Failed to produce to topic '%s': %s", r.OutputRoomEventTopic, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var roomserverInputBackpressure = prometheus.NewGaugeVec(
|
var roomserverInputBackpressure = prometheus.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Namespace: "dendrite",
|
Namespace: "dendrite",
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
"github.com/opentracing/opentracing-go"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
@ -75,6 +76,11 @@ func (r *Inputer) processRoomEvent(
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "processRoomEvent")
|
||||||
|
span.SetTag("room_id", input.Event.RoomID())
|
||||||
|
span.SetTag("event_id", input.Event.EventID())
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
// Measure how long it takes to process this event.
|
// Measure how long it takes to process this event.
|
||||||
started := time.Now()
|
started := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|
@ -289,6 +295,22 @@ func (r *Inputer) processRoomEvent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the state before the event so that we can work out if the event was
|
||||||
|
// allowed at the time, and also to get the history visibility. We won't
|
||||||
|
// bother doing this if the event was already rejected as it just ends up
|
||||||
|
// burning CPU time.
|
||||||
|
historyVisibility := gomatrixserverlib.HistoryVisibilityJoined // Default to restrictive.
|
||||||
|
if rejectionErr == nil && !isRejected && !softfail {
|
||||||
|
var err error
|
||||||
|
historyVisibility, rejectionErr, err = r.processStateBefore(ctx, input, missingPrev)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("r.processStateBefore: %w", err)
|
||||||
|
}
|
||||||
|
if rejectionErr != nil {
|
||||||
|
isRejected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Store the event.
|
// Store the event.
|
||||||
_, _, stateAtEvent, redactionEvent, redactedEventID, err := r.DB.StoreEvent(ctx, event, authEventNIDs, isRejected || softfail)
|
_, _, stateAtEvent, redactionEvent, redactedEventID, err := r.DB.StoreEvent(ctx, event, authEventNIDs, isRejected || softfail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -354,15 +376,17 @@ func (r *Inputer) processRoomEvent(
|
||||||
input.SendAsServer, // send as server
|
input.SendAsServer, // send as server
|
||||||
input.TransactionID, // transaction ID
|
input.TransactionID, // transaction ID
|
||||||
input.HasState, // rewrites state?
|
input.HasState, // rewrites state?
|
||||||
|
historyVisibility, // the history visibility before the event
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("r.updateLatestEvents: %w", err)
|
return fmt.Errorf("r.updateLatestEvents: %w", err)
|
||||||
}
|
}
|
||||||
case api.KindOld:
|
case api.KindOld:
|
||||||
err = r.WriteOutputEvents(event.RoomID(), []api.OutputEvent{
|
err = r.OutputProducer.ProduceRoomEvents(event.RoomID(), []api.OutputEvent{
|
||||||
{
|
{
|
||||||
Type: api.OutputTypeOldRoomEvent,
|
Type: api.OutputTypeOldRoomEvent,
|
||||||
OldRoomEvent: &api.OutputOldRoomEvent{
|
OldRoomEvent: &api.OutputOldRoomEvent{
|
||||||
Event: headered,
|
Event: headered,
|
||||||
|
HistoryVisibility: historyVisibility,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -376,7 +400,7 @@ func (r *Inputer) processRoomEvent(
|
||||||
// so notify downstream components to redact this event - they should have it if they've
|
// so notify downstream components to redact this event - they should have it if they've
|
||||||
// been tracking our output log.
|
// been tracking our output log.
|
||||||
if redactedEventID != "" {
|
if redactedEventID != "" {
|
||||||
err = r.WriteOutputEvents(event.RoomID(), []api.OutputEvent{
|
err = r.OutputProducer.ProduceRoomEvents(event.RoomID(), []api.OutputEvent{
|
||||||
{
|
{
|
||||||
Type: api.OutputTypeRedactedEvent,
|
Type: api.OutputTypeRedactedEvent,
|
||||||
RedactedEvent: &api.OutputRedactedEvent{
|
RedactedEvent: &api.OutputRedactedEvent{
|
||||||
|
|
@ -396,6 +420,100 @@ func (r *Inputer) processRoomEvent(
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// processStateBefore works out what the state is before the event and
|
||||||
|
// then checks the event auths against the state at the time. It also
|
||||||
|
// tries to determine what the history visibility was of the event at
|
||||||
|
// the time, so that it can be sent in the output event to downstream
|
||||||
|
// components.
|
||||||
|
// nolint:nakedret
|
||||||
|
func (r *Inputer) processStateBefore(
|
||||||
|
ctx context.Context,
|
||||||
|
input *api.InputRoomEvent,
|
||||||
|
missingPrev bool,
|
||||||
|
) (historyVisibility gomatrixserverlib.HistoryVisibility, rejectionErr error, err error) {
|
||||||
|
historyVisibility = gomatrixserverlib.HistoryVisibilityJoined // Default to restrictive.
|
||||||
|
event := input.Event.Unwrap()
|
||||||
|
isCreateEvent := event.Type() == gomatrixserverlib.MRoomCreate && event.StateKeyEquals("")
|
||||||
|
var stateBeforeEvent []*gomatrixserverlib.Event
|
||||||
|
switch {
|
||||||
|
case isCreateEvent:
|
||||||
|
// There's no state before a create event so there is nothing
|
||||||
|
// else to do.
|
||||||
|
return
|
||||||
|
case input.HasState:
|
||||||
|
// If we're overriding the state then we need to go and retrieve
|
||||||
|
// them from the database. It's a hard error if they are missing.
|
||||||
|
stateEvents, err := r.DB.EventsFromIDs(ctx, input.StateEventIDs)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, fmt.Errorf("r.DB.EventsFromIDs: %w", err)
|
||||||
|
}
|
||||||
|
stateBeforeEvent = make([]*gomatrixserverlib.Event, 0, len(stateEvents))
|
||||||
|
for _, entry := range stateEvents {
|
||||||
|
stateBeforeEvent = append(stateBeforeEvent, entry.Event)
|
||||||
|
}
|
||||||
|
case missingPrev:
|
||||||
|
// We don't know all of the prev events, so we can't work out
|
||||||
|
// the state before the event. Reject it in that case.
|
||||||
|
rejectionErr = fmt.Errorf("event %q has missing prev events", event.EventID())
|
||||||
|
return
|
||||||
|
case len(event.PrevEventIDs()) == 0:
|
||||||
|
// There should be prev events since it's not a create event.
|
||||||
|
// A non-create event that claims to have no prev events is
|
||||||
|
// invalid, so reject it.
|
||||||
|
rejectionErr = fmt.Errorf("event %q must have prev events", event.EventID())
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
// For all non-create events, there must be prev events, so we'll
|
||||||
|
// ask the query API for the relevant tuples needed for auth. We
|
||||||
|
// will include the history visibility here even though we don't
|
||||||
|
// actually need it for auth, because we want to send it in the
|
||||||
|
// output events.
|
||||||
|
tuplesNeeded := gomatrixserverlib.StateNeededForAuth([]*gomatrixserverlib.Event{event}).Tuples()
|
||||||
|
tuplesNeeded = append(tuplesNeeded, gomatrixserverlib.StateKeyTuple{
|
||||||
|
EventType: gomatrixserverlib.MRoomHistoryVisibility,
|
||||||
|
StateKey: "",
|
||||||
|
})
|
||||||
|
stateBeforeReq := &api.QueryStateAfterEventsRequest{
|
||||||
|
RoomID: event.RoomID(),
|
||||||
|
PrevEventIDs: event.PrevEventIDs(),
|
||||||
|
StateToFetch: tuplesNeeded,
|
||||||
|
}
|
||||||
|
stateBeforeRes := &api.QueryStateAfterEventsResponse{}
|
||||||
|
if err := r.Queryer.QueryStateAfterEvents(ctx, stateBeforeReq, stateBeforeRes); err != nil {
|
||||||
|
return "", nil, fmt.Errorf("r.Queryer.QueryStateAfterEvents: %w", err)
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case !stateBeforeRes.RoomExists:
|
||||||
|
rejectionErr = fmt.Errorf("room %q does not exist", event.RoomID())
|
||||||
|
return
|
||||||
|
case !stateBeforeRes.PrevEventsExist:
|
||||||
|
rejectionErr = fmt.Errorf("prev events of %q are not known", event.EventID())
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
stateBeforeEvent = gomatrixserverlib.UnwrapEventHeaders(stateBeforeRes.StateEvents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// At this point, stateBeforeEvent should be populated either by
|
||||||
|
// the supplied state in the input request, or from the prev events.
|
||||||
|
// Check whether the event is allowed or not.
|
||||||
|
stateBeforeAuth := gomatrixserverlib.NewAuthEvents(stateBeforeEvent)
|
||||||
|
if rejectionErr = gomatrixserverlib.Allowed(event, &stateBeforeAuth); rejectionErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Work out what the history visibility was at the time of the
|
||||||
|
// event.
|
||||||
|
for _, event := range stateBeforeEvent {
|
||||||
|
if event.Type() != gomatrixserverlib.MRoomHistoryVisibility || !event.StateKeyEquals("") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if hisVis, err := event.HistoryVisibility(); err == nil {
|
||||||
|
historyVisibility = hisVis
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// fetchAuthEvents will check to see if any of the
|
// fetchAuthEvents will check to see if any of the
|
||||||
// auth events specified by the given event are unknown. If they are
|
// auth events specified by the given event are unknown. If they are
|
||||||
// then we will go off and request them from the federation and then
|
// then we will go off and request them from the federation and then
|
||||||
|
|
@ -411,6 +529,9 @@ func (r *Inputer) fetchAuthEvents(
|
||||||
known map[string]*types.Event,
|
known map[string]*types.Event,
|
||||||
servers []gomatrixserverlib.ServerName,
|
servers []gomatrixserverlib.ServerName,
|
||||||
) error {
|
) error {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "fetchAuthEvents")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
unknown := map[string]struct{}{}
|
unknown := map[string]struct{}{}
|
||||||
authEventIDs := event.AuthEventIDs()
|
authEventIDs := event.AuthEventIDs()
|
||||||
if len(authEventIDs) == 0 {
|
if len(authEventIDs) == 0 {
|
||||||
|
|
@ -526,6 +647,9 @@ func (r *Inputer) calculateAndSetState(
|
||||||
event *gomatrixserverlib.Event,
|
event *gomatrixserverlib.Event,
|
||||||
isRejected bool,
|
isRejected bool,
|
||||||
) error {
|
) error {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "calculateAndSetState")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
var succeeded bool
|
var succeeded bool
|
||||||
updater, err := r.DB.GetRoomUpdater(ctx, roomInfo)
|
updater, err := r.DB.GetRoomUpdater(ctx, roomInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
"github.com/opentracing/opentracing-go"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -55,7 +56,11 @@ func (r *Inputer) updateLatestEvents(
|
||||||
sendAsServer string,
|
sendAsServer string,
|
||||||
transactionID *api.TransactionID,
|
transactionID *api.TransactionID,
|
||||||
rewritesState bool,
|
rewritesState bool,
|
||||||
|
historyVisibility gomatrixserverlib.HistoryVisibility,
|
||||||
) (err error) {
|
) (err error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "updateLatestEvents")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
var succeeded bool
|
var succeeded bool
|
||||||
updater, err := r.DB.GetRoomUpdater(ctx, roomInfo)
|
updater, err := r.DB.GetRoomUpdater(ctx, roomInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -65,15 +70,16 @@ func (r *Inputer) updateLatestEvents(
|
||||||
defer sqlutil.EndTransactionWithCheck(updater, &succeeded, &err)
|
defer sqlutil.EndTransactionWithCheck(updater, &succeeded, &err)
|
||||||
|
|
||||||
u := latestEventsUpdater{
|
u := latestEventsUpdater{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
api: r,
|
api: r,
|
||||||
updater: updater,
|
updater: updater,
|
||||||
roomInfo: roomInfo,
|
roomInfo: roomInfo,
|
||||||
stateAtEvent: stateAtEvent,
|
stateAtEvent: stateAtEvent,
|
||||||
event: event,
|
event: event,
|
||||||
sendAsServer: sendAsServer,
|
sendAsServer: sendAsServer,
|
||||||
transactionID: transactionID,
|
transactionID: transactionID,
|
||||||
rewritesState: rewritesState,
|
rewritesState: rewritesState,
|
||||||
|
historyVisibility: historyVisibility,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = u.doUpdateLatestEvents(); err != nil {
|
if err = u.doUpdateLatestEvents(); err != nil {
|
||||||
|
|
@ -115,6 +121,8 @@ type latestEventsUpdater struct {
|
||||||
// The snapshots of current state before and after processing this event
|
// The snapshots of current state before and after processing this event
|
||||||
oldStateNID types.StateSnapshotNID
|
oldStateNID types.StateSnapshotNID
|
||||||
newStateNID types.StateSnapshotNID
|
newStateNID types.StateSnapshotNID
|
||||||
|
// The history visibility of the event itself (from the state before the event).
|
||||||
|
historyVisibility gomatrixserverlib.HistoryVisibility
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *latestEventsUpdater) doUpdateLatestEvents() error {
|
func (u *latestEventsUpdater) doUpdateLatestEvents() error {
|
||||||
|
|
@ -184,7 +192,7 @@ func (u *latestEventsUpdater) doUpdateLatestEvents() error {
|
||||||
// send the event asynchronously but we would need to ensure that 1) the events are written to the log in
|
// send the event asynchronously but we would need to ensure that 1) the events are written to the log in
|
||||||
// the correct order, 2) that pending writes are resent across restarts. In order to avoid writing all the
|
// the correct order, 2) that pending writes are resent across restarts. In order to avoid writing all the
|
||||||
// necessary bookkeeping we'll keep the event sending synchronous for now.
|
// necessary bookkeeping we'll keep the event sending synchronous for now.
|
||||||
if err = u.api.WriteOutputEvents(u.event.RoomID(), updates); err != nil {
|
if err = u.api.OutputProducer.ProduceRoomEvents(u.event.RoomID(), updates); err != nil {
|
||||||
return fmt.Errorf("u.api.WriteOutputEvents: %w", err)
|
return fmt.Errorf("u.api.WriteOutputEvents: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,13 +208,16 @@ func (u *latestEventsUpdater) doUpdateLatestEvents() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *latestEventsUpdater) latestState() error {
|
func (u *latestEventsUpdater) latestState() error {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(u.ctx, "processEventWithMissingState")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
roomState := state.NewStateResolution(u.updater, u.roomInfo)
|
roomState := state.NewStateResolution(u.updater, u.roomInfo)
|
||||||
|
|
||||||
// Work out if the state at the extremities has actually changed
|
// Work out if the state at the extremities has actually changed
|
||||||
// or not. If they haven't then we won't bother doing all of the
|
// or not. If they haven't then we won't bother doing all of the
|
||||||
// hard work.
|
// hard work.
|
||||||
if u.event.StateKey() == nil {
|
if !u.stateAtEvent.IsStateEvent() {
|
||||||
stateChanged := false
|
stateChanged := false
|
||||||
oldStateNIDs := make([]types.StateSnapshotNID, 0, len(u.oldLatest))
|
oldStateNIDs := make([]types.StateSnapshotNID, 0, len(u.oldLatest))
|
||||||
newStateNIDs := make([]types.StateSnapshotNID, 0, len(u.latest))
|
newStateNIDs := make([]types.StateSnapshotNID, 0, len(u.latest))
|
||||||
|
|
@ -234,26 +245,19 @@ func (u *latestEventsUpdater) latestState() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take the old set of extremities and the new set of extremities and
|
// Get a list of the current latest events. This may or may not
|
||||||
// mash them together into a list. This may or may not include the new event
|
// include the new event from the input path, depending on whether
|
||||||
// from the input path, depending on whether it became a forward extremity
|
// it is a forward extremity or not.
|
||||||
// or not. We'll then run state resolution across all of them to determine
|
latestStateAtEvents := make([]types.StateAtEvent, len(u.latest))
|
||||||
// the new current state of the room. Including the old extremities here
|
for i := range u.latest {
|
||||||
// ensures that new forward extremities with bad state snapshots (from
|
latestStateAtEvents[i] = u.latest[i].StateAtEvent
|
||||||
// possible malicious actors) can't completely corrupt the room state
|
|
||||||
// away from what it was before.
|
|
||||||
combinedExtremities := types.StateAtEventAndReferences(append(u.oldLatest, u.latest...))
|
|
||||||
combinedExtremities = combinedExtremities[:util.SortAndUnique(combinedExtremities)]
|
|
||||||
latestStateAtEvents := make([]types.StateAtEvent, len(combinedExtremities))
|
|
||||||
for i := range combinedExtremities {
|
|
||||||
latestStateAtEvents[i] = combinedExtremities[i].StateAtEvent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Takes the NIDs of the latest events and creates a state snapshot
|
// Takes the NIDs of the latest events and creates a state snapshot
|
||||||
// of the state after the events. The snapshot state will be resolved
|
// of the state after the events. The snapshot state will be resolved
|
||||||
// using the correct state resolution algorithm for the room.
|
// using the correct state resolution algorithm for the room.
|
||||||
u.newStateNID, err = roomState.CalculateAndStoreStateAfterEvents(
|
u.newStateNID, err = roomState.CalculateAndStoreStateAfterEvents(
|
||||||
u.ctx, latestStateAtEvents,
|
ctx, latestStateAtEvents,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("roomState.CalculateAndStoreStateAfterEvents: %w", err)
|
return fmt.Errorf("roomState.CalculateAndStoreStateAfterEvents: %w", err)
|
||||||
|
|
@ -265,7 +269,7 @@ func (u *latestEventsUpdater) latestState() error {
|
||||||
// another list of added ones. Replacing a value for a state-key tuple
|
// another list of added ones. Replacing a value for a state-key tuple
|
||||||
// will result one removed (the old event) and one added (the new event).
|
// will result one removed (the old event) and one added (the new event).
|
||||||
u.removed, u.added, err = roomState.DifferenceBetweeenStateSnapshots(
|
u.removed, u.added, err = roomState.DifferenceBetweeenStateSnapshots(
|
||||||
u.ctx, u.oldStateNID, u.newStateNID,
|
ctx, u.oldStateNID, u.newStateNID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("roomState.DifferenceBetweenStateSnapshots: %w", err)
|
return fmt.Errorf("roomState.DifferenceBetweenStateSnapshots: %w", err)
|
||||||
|
|
@ -285,7 +289,7 @@ func (u *latestEventsUpdater) latestState() error {
|
||||||
// Also work out the state before the event removes and the event
|
// Also work out the state before the event removes and the event
|
||||||
// adds.
|
// adds.
|
||||||
u.stateBeforeEventRemoves, u.stateBeforeEventAdds, err = roomState.DifferenceBetweeenStateSnapshots(
|
u.stateBeforeEventRemoves, u.stateBeforeEventAdds, err = roomState.DifferenceBetweeenStateSnapshots(
|
||||||
u.ctx, u.newStateNID, u.stateAtEvent.BeforeStateSnapshotNID,
|
ctx, u.newStateNID, u.stateAtEvent.BeforeStateSnapshotNID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("roomState.DifferenceBetweeenStateSnapshots: %w", err)
|
return fmt.Errorf("roomState.DifferenceBetweeenStateSnapshots: %w", err)
|
||||||
|
|
@ -301,6 +305,9 @@ func (u *latestEventsUpdater) calculateLatest(
|
||||||
newEvent *gomatrixserverlib.Event,
|
newEvent *gomatrixserverlib.Event,
|
||||||
newStateAndRef types.StateAtEventAndReference,
|
newStateAndRef types.StateAtEventAndReference,
|
||||||
) (bool, error) {
|
) (bool, error) {
|
||||||
|
span, _ := opentracing.StartSpanFromContext(u.ctx, "calculateLatest")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
// First of all, get a list of all of the events in our current
|
// First of all, get a list of all of the events in our current
|
||||||
// set of forward extremities.
|
// set of forward extremities.
|
||||||
existingRefs := make(map[string]*types.StateAtEventAndReference)
|
existingRefs := make(map[string]*types.StateAtEventAndReference)
|
||||||
|
|
@ -362,12 +369,13 @@ func (u *latestEventsUpdater) makeOutputNewRoomEvent() (*api.OutputEvent, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
ore := api.OutputNewRoomEvent{
|
ore := api.OutputNewRoomEvent{
|
||||||
Event: u.event.Headered(u.roomInfo.RoomVersion),
|
Event: u.event.Headered(u.roomInfo.RoomVersion),
|
||||||
RewritesState: u.rewritesState,
|
RewritesState: u.rewritesState,
|
||||||
LastSentEventID: u.lastEventIDSent,
|
LastSentEventID: u.lastEventIDSent,
|
||||||
LatestEventIDs: latestEventIDs,
|
LatestEventIDs: latestEventIDs,
|
||||||
TransactionID: u.transactionID,
|
TransactionID: u.transactionID,
|
||||||
SendAsServer: u.sendAsServer,
|
SendAsServer: u.sendAsServer,
|
||||||
|
HistoryVisibility: u.historyVisibility,
|
||||||
}
|
}
|
||||||
|
|
||||||
eventIDMap, err := u.stateEventMap()
|
eventIDMap, err := u.stateEventMap()
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/roomserver/storage/shared"
|
"github.com/matrix-org/dendrite/roomserver/storage/shared"
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/opentracing/opentracing-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
// updateMembership updates the current membership and the invites for each
|
// updateMembership updates the current membership and the invites for each
|
||||||
|
|
@ -34,6 +35,9 @@ func (r *Inputer) updateMemberships(
|
||||||
updater *shared.RoomUpdater,
|
updater *shared.RoomUpdater,
|
||||||
removed, added []types.StateEntry,
|
removed, added []types.StateEntry,
|
||||||
) ([]api.OutputEvent, error) {
|
) ([]api.OutputEvent, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "updateMemberships")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
changes := membershipChanges(removed, added)
|
changes := membershipChanges(removed, added)
|
||||||
var eventNIDs []types.EventNID
|
var eventNIDs []types.EventNID
|
||||||
for _, change := range changes {
|
for _, change := range changes {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
"github.com/opentracing/opentracing-go"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -59,6 +60,9 @@ type missingStateReq struct {
|
||||||
func (t *missingStateReq) processEventWithMissingState(
|
func (t *missingStateReq) processEventWithMissingState(
|
||||||
ctx context.Context, e *gomatrixserverlib.Event, roomVersion gomatrixserverlib.RoomVersion,
|
ctx context.Context, e *gomatrixserverlib.Event, roomVersion gomatrixserverlib.RoomVersion,
|
||||||
) (*parsedRespState, error) {
|
) (*parsedRespState, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "processEventWithMissingState")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
// We are missing the previous events for this events.
|
// We are missing the previous events for this events.
|
||||||
// This means that there is a gap in our view of the history of the
|
// This means that there is a gap in our view of the history of the
|
||||||
// room. There two ways that we can handle such a gap:
|
// room. There two ways that we can handle such a gap:
|
||||||
|
|
@ -235,6 +239,9 @@ func (t *missingStateReq) processEventWithMissingState(
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *missingStateReq) lookupResolvedStateBeforeEvent(ctx context.Context, e *gomatrixserverlib.Event, roomVersion gomatrixserverlib.RoomVersion) (*parsedRespState, error) {
|
func (t *missingStateReq) lookupResolvedStateBeforeEvent(ctx context.Context, e *gomatrixserverlib.Event, roomVersion gomatrixserverlib.RoomVersion) (*parsedRespState, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "lookupResolvedStateBeforeEvent")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
type respState struct {
|
type respState struct {
|
||||||
// A snapshot is considered trustworthy if it came from our own roomserver.
|
// A snapshot is considered trustworthy if it came from our own roomserver.
|
||||||
// That's because the state will have been through state resolution once
|
// That's because the state will have been through state resolution once
|
||||||
|
|
@ -310,6 +317,9 @@ func (t *missingStateReq) lookupResolvedStateBeforeEvent(ctx context.Context, e
|
||||||
// lookupStateAfterEvent returns the room state after `eventID`, which is the state before eventID with the state of `eventID` (if it's a state event)
|
// lookupStateAfterEvent returns the room state after `eventID`, which is the state before eventID with the state of `eventID` (if it's a state event)
|
||||||
// added into the mix.
|
// added into the mix.
|
||||||
func (t *missingStateReq) lookupStateAfterEvent(ctx context.Context, roomVersion gomatrixserverlib.RoomVersion, roomID, eventID string) (*parsedRespState, bool, error) {
|
func (t *missingStateReq) lookupStateAfterEvent(ctx context.Context, roomVersion gomatrixserverlib.RoomVersion, roomID, eventID string) (*parsedRespState, bool, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "lookupStateAfterEvent")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
// try doing all this locally before we resort to querying federation
|
// try doing all this locally before we resort to querying federation
|
||||||
respState := t.lookupStateAfterEventLocally(ctx, roomID, eventID)
|
respState := t.lookupStateAfterEventLocally(ctx, roomID, eventID)
|
||||||
if respState != nil {
|
if respState != nil {
|
||||||
|
|
@ -361,6 +371,9 @@ func (t *missingStateReq) cacheAndReturn(ev *gomatrixserverlib.Event) *gomatrixs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *missingStateReq) lookupStateAfterEventLocally(ctx context.Context, roomID, eventID string) *parsedRespState {
|
func (t *missingStateReq) lookupStateAfterEventLocally(ctx context.Context, roomID, eventID string) *parsedRespState {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "lookupStateAfterEventLocally")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
var res parsedRespState
|
var res parsedRespState
|
||||||
roomInfo, err := t.db.RoomInfo(ctx, roomID)
|
roomInfo, err := t.db.RoomInfo(ctx, roomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -435,12 +448,17 @@ func (t *missingStateReq) lookupStateAfterEventLocally(ctx context.Context, room
|
||||||
// the server supports.
|
// the server supports.
|
||||||
func (t *missingStateReq) lookupStateBeforeEvent(ctx context.Context, roomVersion gomatrixserverlib.RoomVersion, roomID, eventID string) (
|
func (t *missingStateReq) lookupStateBeforeEvent(ctx context.Context, roomVersion gomatrixserverlib.RoomVersion, roomID, eventID string) (
|
||||||
*parsedRespState, error) {
|
*parsedRespState, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "lookupStateBeforeEvent")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
// Attempt to fetch the missing state using /state_ids and /events
|
// Attempt to fetch the missing state using /state_ids and /events
|
||||||
return t.lookupMissingStateViaStateIDs(ctx, roomID, eventID, roomVersion)
|
return t.lookupMissingStateViaStateIDs(ctx, roomID, eventID, roomVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *missingStateReq) resolveStatesAndCheck(ctx context.Context, roomVersion gomatrixserverlib.RoomVersion, states []*parsedRespState, backwardsExtremity *gomatrixserverlib.Event) (*parsedRespState, error) {
|
func (t *missingStateReq) resolveStatesAndCheck(ctx context.Context, roomVersion gomatrixserverlib.RoomVersion, states []*parsedRespState, backwardsExtremity *gomatrixserverlib.Event) (*parsedRespState, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "resolveStatesAndCheck")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
var authEventList []*gomatrixserverlib.Event
|
var authEventList []*gomatrixserverlib.Event
|
||||||
var stateEventList []*gomatrixserverlib.Event
|
var stateEventList []*gomatrixserverlib.Event
|
||||||
for _, state := range states {
|
for _, state := range states {
|
||||||
|
|
@ -484,6 +502,9 @@ retryAllowedState:
|
||||||
// get missing events for `e`. If `isGapFilled`=true then `newEvents` contains all the events to inject,
|
// get missing events for `e`. If `isGapFilled`=true then `newEvents` contains all the events to inject,
|
||||||
// without `e`. If `isGapFilled=false` then `newEvents` contains the response to /get_missing_events
|
// without `e`. If `isGapFilled=false` then `newEvents` contains the response to /get_missing_events
|
||||||
func (t *missingStateReq) getMissingEvents(ctx context.Context, e *gomatrixserverlib.Event, roomVersion gomatrixserverlib.RoomVersion) (newEvents []*gomatrixserverlib.Event, isGapFilled, prevStateKnown bool, err error) {
|
func (t *missingStateReq) getMissingEvents(ctx context.Context, e *gomatrixserverlib.Event, roomVersion gomatrixserverlib.RoomVersion) (newEvents []*gomatrixserverlib.Event, isGapFilled, prevStateKnown bool, err error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "getMissingEvents")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
logger := util.GetLogger(ctx).WithField("event_id", e.EventID()).WithField("room_id", e.RoomID())
|
logger := util.GetLogger(ctx).WithField("event_id", e.EventID()).WithField("room_id", e.RoomID())
|
||||||
latest, _, _, err := t.db.LatestEventIDs(ctx, t.roomInfo.RoomNID)
|
latest, _, _, err := t.db.LatestEventIDs(ctx, t.roomInfo.RoomNID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -608,6 +629,9 @@ func (t *missingStateReq) isPrevStateKnown(ctx context.Context, e *gomatrixserve
|
||||||
func (t *missingStateReq) lookupMissingStateViaState(
|
func (t *missingStateReq) lookupMissingStateViaState(
|
||||||
ctx context.Context, roomID, eventID string, roomVersion gomatrixserverlib.RoomVersion,
|
ctx context.Context, roomID, eventID string, roomVersion gomatrixserverlib.RoomVersion,
|
||||||
) (respState *parsedRespState, err error) {
|
) (respState *parsedRespState, err error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "lookupMissingStateViaState")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
state, err := t.federation.LookupState(ctx, t.origin, roomID, eventID, roomVersion)
|
state, err := t.federation.LookupState(ctx, t.origin, roomID, eventID, roomVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -637,6 +661,9 @@ func (t *missingStateReq) lookupMissingStateViaState(
|
||||||
|
|
||||||
func (t *missingStateReq) lookupMissingStateViaStateIDs(ctx context.Context, roomID, eventID string, roomVersion gomatrixserverlib.RoomVersion) (
|
func (t *missingStateReq) lookupMissingStateViaStateIDs(ctx context.Context, roomID, eventID string, roomVersion gomatrixserverlib.RoomVersion) (
|
||||||
*parsedRespState, error) {
|
*parsedRespState, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "lookupMissingStateViaStateIDs")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
util.GetLogger(ctx).WithField("room_id", roomID).Infof("lookupMissingStateViaStateIDs %s", eventID)
|
util.GetLogger(ctx).WithField("room_id", roomID).Infof("lookupMissingStateViaStateIDs %s", eventID)
|
||||||
// fetch the state event IDs at the time of the event
|
// fetch the state event IDs at the time of the event
|
||||||
stateIDs, err := t.federation.LookupStateIDs(ctx, t.origin, roomID, eventID)
|
stateIDs, err := t.federation.LookupStateIDs(ctx, t.origin, roomID, eventID)
|
||||||
|
|
@ -799,6 +826,9 @@ func (t *missingStateReq) createRespStateFromStateIDs(
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *missingStateReq) lookupEvent(ctx context.Context, roomVersion gomatrixserverlib.RoomVersion, _, missingEventID string, localFirst bool) (*gomatrixserverlib.Event, error) {
|
func (t *missingStateReq) lookupEvent(ctx context.Context, roomVersion gomatrixserverlib.RoomVersion, _, missingEventID string, localFirst bool) (*gomatrixserverlib.Event, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "lookupEvent")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
if localFirst {
|
if localFirst {
|
||||||
// fetch from the roomserver
|
// fetch from the roomserver
|
||||||
events, err := t.db.EventsFromIDs(ctx, []string{missingEventID})
|
events, err := t.db.EventsFromIDs(ctx, []string{missingEventID})
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ package perform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -34,6 +35,7 @@ type Admin struct {
|
||||||
Cfg *config.RoomServer
|
Cfg *config.RoomServer
|
||||||
Queryer *query.Queryer
|
Queryer *query.Queryer
|
||||||
Inputer *input.Inputer
|
Inputer *input.Inputer
|
||||||
|
Leaver *Leaver
|
||||||
}
|
}
|
||||||
|
|
||||||
// PerformEvacuateRoom will remove all local users from the given room.
|
// PerformEvacuateRoom will remove all local users from the given room.
|
||||||
|
|
@ -160,3 +162,71 @@ func (r *Admin) PerformAdminEvacuateRoom(
|
||||||
inputRes := &api.InputRoomEventsResponse{}
|
inputRes := &api.InputRoomEventsResponse{}
|
||||||
r.Inputer.InputRoomEvents(ctx, inputReq, inputRes)
|
r.Inputer.InputRoomEvents(ctx, inputReq, inputRes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Admin) PerformAdminEvacuateUser(
|
||||||
|
ctx context.Context,
|
||||||
|
req *api.PerformAdminEvacuateUserRequest,
|
||||||
|
res *api.PerformAdminEvacuateUserResponse,
|
||||||
|
) {
|
||||||
|
_, domain, err := gomatrixserverlib.SplitID('@', req.UserID)
|
||||||
|
if err != nil {
|
||||||
|
res.Error = &api.PerformError{
|
||||||
|
Code: api.PerformErrorBadRequest,
|
||||||
|
Msg: fmt.Sprintf("Malformed user ID: %s", err),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if domain != r.Cfg.Matrix.ServerName {
|
||||||
|
res.Error = &api.PerformError{
|
||||||
|
Code: api.PerformErrorBadRequest,
|
||||||
|
Msg: "Can only evacuate local users using this endpoint",
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
roomIDs, err := r.DB.GetRoomsByMembership(ctx, req.UserID, gomatrixserverlib.Join)
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
res.Error = &api.PerformError{
|
||||||
|
Code: api.PerformErrorBadRequest,
|
||||||
|
Msg: fmt.Sprintf("r.DB.GetRoomsByMembership: %s", err),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
inviteRoomIDs, err := r.DB.GetRoomsByMembership(ctx, req.UserID, gomatrixserverlib.Invite)
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
res.Error = &api.PerformError{
|
||||||
|
Code: api.PerformErrorBadRequest,
|
||||||
|
Msg: fmt.Sprintf("r.DB.GetRoomsByMembership: %s", err),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, roomID := range append(roomIDs, inviteRoomIDs...) {
|
||||||
|
leaveReq := &api.PerformLeaveRequest{
|
||||||
|
RoomID: roomID,
|
||||||
|
UserID: req.UserID,
|
||||||
|
}
|
||||||
|
leaveRes := &api.PerformLeaveResponse{}
|
||||||
|
outputEvents, err := r.Leaver.PerformLeave(ctx, leaveReq, leaveRes)
|
||||||
|
if err != nil {
|
||||||
|
res.Error = &api.PerformError{
|
||||||
|
Code: api.PerformErrorBadRequest,
|
||||||
|
Msg: fmt.Sprintf("r.Leaver.PerformLeave: %s", err),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(outputEvents) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := r.Inputer.OutputProducer.ProduceRoomEvents(roomID, outputEvents); err != nil {
|
||||||
|
res.Error = &api.PerformError{
|
||||||
|
Code: api.PerformErrorBadRequest,
|
||||||
|
Msg: fmt.Sprintf("r.Inputer.WriteOutputEvents: %s", err),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Affected = append(res.Affected, roomID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
||||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
|
@ -206,8 +207,17 @@ func (r *Backfiller) fetchAndStoreMissingEvents(ctx context.Context, roomVer gom
|
||||||
}
|
}
|
||||||
logger.Infof("returned %d PDUs which made events %+v", len(res.PDUs), result)
|
logger.Infof("returned %d PDUs which made events %+v", len(res.PDUs), result)
|
||||||
for _, res := range result {
|
for _, res := range result {
|
||||||
if res.Error != nil {
|
switch err := res.Error.(type) {
|
||||||
logger.WithError(res.Error).Warn("event failed PDU checks")
|
case nil:
|
||||||
|
case gomatrixserverlib.SignatureErr:
|
||||||
|
// The signature of the event might not be valid anymore, for example if
|
||||||
|
// the key ID was reused with a different signature.
|
||||||
|
logger.WithError(err).Errorf("event failed PDU checks, storing anyway")
|
||||||
|
case gomatrixserverlib.AuthChainErr, gomatrixserverlib.AuthRulesErr:
|
||||||
|
logger.WithError(err).Warn("event failed PDU checks")
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
logger.WithError(err).Warn("event failed PDU checks")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
missingMap[id] = res.Event
|
missingMap[id] = res.Event
|
||||||
|
|
@ -306,6 +316,7 @@ FederationHit:
|
||||||
b.eventIDToBeforeStateIDs[targetEvent.EventID()] = res
|
b.eventIDToBeforeStateIDs[targetEvent.EventID()] = res
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
sentry.CaptureException(lastErr) // temporary to see if we might need to raise the server limit
|
||||||
return nil, lastErr
|
return nil, lastErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -366,19 +377,25 @@ func (b *backfillRequester) StateBeforeEvent(ctx context.Context, roomVer gomatr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c := gomatrixserverlib.FederatedStateProvider{
|
var lastErr error
|
||||||
FedClient: b.fsAPI,
|
for _, srv := range b.servers {
|
||||||
RememberAuthEvents: false,
|
c := gomatrixserverlib.FederatedStateProvider{
|
||||||
Server: b.servers[0],
|
FedClient: b.fsAPI,
|
||||||
|
RememberAuthEvents: false,
|
||||||
|
Server: srv,
|
||||||
|
}
|
||||||
|
result, err := c.StateBeforeEvent(ctx, roomVer, event, eventIDs)
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for eventID, ev := range result {
|
||||||
|
b.eventIDMap[eventID] = ev
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
result, err := c.StateBeforeEvent(ctx, roomVer, event, eventIDs)
|
sentry.CaptureException(lastErr) // temporary to see if we might need to raise the server limit
|
||||||
if err != nil {
|
return nil, lastErr
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for eventID, ev := range result {
|
|
||||||
b.eventIDMap[eventID] = ev
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServersAtEvent is called when trying to determine which server to request from.
|
// ServersAtEvent is called when trying to determine which server to request from.
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ func (r *InboundPeeker) PerformInboundPeek(
|
||||||
response.AuthChainEvents = append(response.AuthChainEvents, event.Headered(info.RoomVersion))
|
response.AuthChainEvents = append(response.AuthChainEvents, event.Headered(info.RoomVersion))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = r.Inputer.WriteOutputEvents(request.RoomID, []api.OutputEvent{
|
err = r.Inputer.OutputProducer.ProduceRoomEvents(request.RoomID, []api.OutputEvent{
|
||||||
{
|
{
|
||||||
Type: api.OutputTypeNewInboundPeek,
|
Type: api.OutputTypeNewInboundPeek,
|
||||||
NewInboundPeek: &api.OutputNewInboundPeek{
|
NewInboundPeek: &api.OutputNewInboundPeek{
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,14 @@ func (r *Inviter) PerformInvite(
|
||||||
return nil, fmt.Errorf("failed to load RoomInfo: %w", err)
|
return nil, fmt.Errorf("failed to load RoomInfo: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, domain, _ := gomatrixserverlib.SplitID('@', targetUserID)
|
_, domain, err := gomatrixserverlib.SplitID('@', targetUserID)
|
||||||
|
if err != nil {
|
||||||
|
res.Error = &api.PerformError{
|
||||||
|
Code: api.PerformErrorBadRequest,
|
||||||
|
Msg: fmt.Sprintf("The user ID %q is invalid!", targetUserID),
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
isTargetLocal := domain == r.Cfg.Matrix.ServerName
|
isTargetLocal := domain == r.Cfg.Matrix.ServerName
|
||||||
isOriginLocal := event.Origin() == r.Cfg.Matrix.ServerName
|
isOriginLocal := event.Origin() == r.Cfg.Matrix.ServerName
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,7 @@ func (r *Peeker) performPeekRoomByID(
|
||||||
|
|
||||||
// TODO: handle federated peeks
|
// TODO: handle federated peeks
|
||||||
|
|
||||||
err = r.Inputer.WriteOutputEvents(roomID, []api.OutputEvent{
|
err = r.Inputer.OutputProducer.ProduceRoomEvents(roomID, []api.OutputEvent{
|
||||||
{
|
{
|
||||||
Type: api.OutputTypeNewPeek,
|
Type: api.OutputTypeNewPeek,
|
||||||
NewPeek: &api.OutputNewPeek{
|
NewPeek: &api.OutputNewPeek{
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ func (r *Unpeeker) performUnpeekRoomByID(
|
||||||
|
|
||||||
// TODO: handle federated peeks
|
// TODO: handle federated peeks
|
||||||
|
|
||||||
err = r.Inputer.WriteOutputEvents(req.RoomID, []api.OutputEvent{
|
err = r.Inputer.OutputProducer.ProduceRoomEvents(req.RoomID, []api.OutputEvent{
|
||||||
{
|
{
|
||||||
Type: api.OutputTypeRetirePeek,
|
Type: api.OutputTypeRetirePeek,
|
||||||
RetirePeek: &api.OutputRetirePeek{
|
RetirePeek: &api.OutputRetirePeek{
|
||||||
|
|
|
||||||
|
|
@ -105,13 +105,13 @@ func (r *Upgrader) performRoomUpgrade(
|
||||||
return "", pErr
|
return "", pErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Send the tombstone event to the old room (must do this before we set the new canonical_alias)
|
// Send the setup events to the new room
|
||||||
if pErr = r.sendHeaderedEvent(ctx, tombstoneEvent); pErr != nil {
|
if pErr = r.sendInitialEvents(ctx, evTime, userID, newRoomID, string(req.RoomVersion), eventsToMake); pErr != nil {
|
||||||
return "", pErr
|
return "", pErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the setup events to the new room
|
// 5. Send the tombstone event to the old room
|
||||||
if pErr = r.sendInitialEvents(ctx, evTime, userID, newRoomID, string(req.RoomVersion), eventsToMake); pErr != nil {
|
if pErr = r.sendHeaderedEvent(ctx, tombstoneEvent, string(r.Cfg.Matrix.ServerName)); pErr != nil {
|
||||||
return "", pErr
|
return "", pErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,7 +147,7 @@ func (r *Upgrader) getRoomPowerLevels(ctx context.Context, roomID string) (*goma
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error()
|
util.GetLogger(ctx).WithError(err).Error()
|
||||||
return nil, &api.PerformError{
|
return nil, &api.PerformError{
|
||||||
Msg: "powerLevel event was not actually a power level event",
|
Msg: "Power level event was invalid or malformed",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return powerLevelContent, nil
|
return powerLevelContent, nil
|
||||||
|
|
@ -182,7 +182,7 @@ func (r *Upgrader) restrictOldRoomPowerLevels(ctx context.Context, evTime time.T
|
||||||
return resErr
|
return resErr
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if resErr = r.sendHeaderedEvent(ctx, restrictedPowerLevelsHeadered); resErr != nil {
|
if resErr = r.sendHeaderedEvent(ctx, restrictedPowerLevelsHeadered, api.DoNotSendToOtherServers); resErr != nil {
|
||||||
return resErr
|
return resErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -198,7 +198,7 @@ func moveLocalAliases(ctx context.Context,
|
||||||
aliasRes := api.GetAliasesForRoomIDResponse{}
|
aliasRes := api.GetAliasesForRoomIDResponse{}
|
||||||
if err = URSAPI.GetAliasesForRoomID(ctx, &aliasReq, &aliasRes); err != nil {
|
if err = URSAPI.GetAliasesForRoomID(ctx, &aliasReq, &aliasRes); err != nil {
|
||||||
return &api.PerformError{
|
return &api.PerformError{
|
||||||
Msg: "Could not get aliases for old room",
|
Msg: fmt.Sprintf("Failed to get old room aliases: %s", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,7 +207,7 @@ func moveLocalAliases(ctx context.Context,
|
||||||
removeAliasRes := api.RemoveRoomAliasResponse{}
|
removeAliasRes := api.RemoveRoomAliasResponse{}
|
||||||
if err = URSAPI.RemoveRoomAlias(ctx, &removeAliasReq, &removeAliasRes); err != nil {
|
if err = URSAPI.RemoveRoomAlias(ctx, &removeAliasReq, &removeAliasRes); err != nil {
|
||||||
return &api.PerformError{
|
return &api.PerformError{
|
||||||
Msg: "api.RemoveRoomAlias failed",
|
Msg: fmt.Sprintf("Failed to remove old room alias: %s", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,7 +215,7 @@ func moveLocalAliases(ctx context.Context,
|
||||||
setAliasRes := api.SetRoomAliasResponse{}
|
setAliasRes := api.SetRoomAliasResponse{}
|
||||||
if err = URSAPI.SetRoomAlias(ctx, &setAliasReq, &setAliasRes); err != nil {
|
if err = URSAPI.SetRoomAlias(ctx, &setAliasReq, &setAliasRes); err != nil {
|
||||||
return &api.PerformError{
|
return &api.PerformError{
|
||||||
Msg: "api.SetRoomAlias failed",
|
Msg: fmt.Sprintf("Failed to set new room alias: %s", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -253,7 +253,7 @@ func (r *Upgrader) clearOldCanonicalAliasEvent(ctx context.Context, oldRoom *api
|
||||||
return resErr
|
return resErr
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if resErr = r.sendHeaderedEvent(ctx, emptyCanonicalAliasEvent); resErr != nil {
|
if resErr = r.sendHeaderedEvent(ctx, emptyCanonicalAliasEvent, api.DoNotSendToOtherServers); resErr != nil {
|
||||||
return resErr
|
return resErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -509,7 +509,7 @@ func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, user
|
||||||
err = builder.SetContent(e.Content)
|
err = builder.SetContent(e.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &api.PerformError{
|
return &api.PerformError{
|
||||||
Msg: "builder.SetContent failed",
|
Msg: fmt.Sprintf("Failed to set content of new %q event: %s", builder.Type, err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
|
|
@ -519,13 +519,13 @@ func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, user
|
||||||
event, err = r.buildEvent(&builder, &authEvents, evTime, gomatrixserverlib.RoomVersion(newVersion))
|
event, err = r.buildEvent(&builder, &authEvents, evTime, gomatrixserverlib.RoomVersion(newVersion))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &api.PerformError{
|
return &api.PerformError{
|
||||||
Msg: "buildEvent failed",
|
Msg: fmt.Sprintf("Failed to build new %q event: %s", builder.Type, err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = gomatrixserverlib.Allowed(event, &authEvents); err != nil {
|
if err = gomatrixserverlib.Allowed(event, &authEvents); err != nil {
|
||||||
return &api.PerformError{
|
return &api.PerformError{
|
||||||
Msg: "gomatrixserverlib.Allowed failed",
|
Msg: fmt.Sprintf("Failed to auth new %q event: %s", builder.Type, err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -534,7 +534,7 @@ func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, user
|
||||||
err = authEvents.AddEvent(event)
|
err = authEvents.AddEvent(event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &api.PerformError{
|
return &api.PerformError{
|
||||||
Msg: "authEvents.AddEvent failed",
|
Msg: fmt.Sprintf("Failed to add new %q event to auth set: %s", builder.Type, err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -550,7 +550,7 @@ func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, user
|
||||||
}
|
}
|
||||||
if err = api.SendInputRoomEvents(ctx, r.URSAPI, inputs, false); err != nil {
|
if err = api.SendInputRoomEvents(ctx, r.URSAPI, inputs, false); err != nil {
|
||||||
return &api.PerformError{
|
return &api.PerformError{
|
||||||
Msg: "api.SendInputRoomEvents failed",
|
Msg: fmt.Sprintf("Failed to send new room %q to roomserver: %s", newRoomID, err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -582,7 +582,7 @@ func (r *Upgrader) makeHeaderedEvent(ctx context.Context, evTime time.Time, user
|
||||||
err := builder.SetContent(event.Content)
|
err := builder.SetContent(event.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &api.PerformError{
|
return nil, &api.PerformError{
|
||||||
Msg: "builder.SetContent failed",
|
Msg: fmt.Sprintf("Failed to set new %q event content: %s", builder.Type, err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var queryRes api.QueryLatestEventsAndStateResponse
|
var queryRes api.QueryLatestEventsAndStateResponse
|
||||||
|
|
@ -607,7 +607,7 @@ func (r *Upgrader) makeHeaderedEvent(ctx context.Context, evTime time.Time, user
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, &api.PerformError{
|
return nil, &api.PerformError{
|
||||||
Msg: "eventutil.BuildEvent failed",
|
Msg: fmt.Sprintf("Failed to build new %q event: %s", builder.Type, err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check to see if this user can perform this operation
|
// check to see if this user can perform this operation
|
||||||
|
|
@ -619,7 +619,7 @@ func (r *Upgrader) makeHeaderedEvent(ctx context.Context, evTime time.Time, user
|
||||||
if err = gomatrixserverlib.Allowed(headeredEvent.Event, &provider); err != nil {
|
if err = gomatrixserverlib.Allowed(headeredEvent.Event, &provider); err != nil {
|
||||||
return nil, &api.PerformError{
|
return nil, &api.PerformError{
|
||||||
Code: api.PerformErrorNotAllowed,
|
Code: api.PerformErrorNotAllowed,
|
||||||
Msg: err.Error(), // TODO: Is this error string comprehensible to the client?
|
Msg: fmt.Sprintf("Failed to auth new %q event: %s", builder.Type, err), // TODO: Is this error string comprehensible to the client?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -666,17 +666,18 @@ func createTemporaryPowerLevels(powerLevelContent *gomatrixserverlib.PowerLevelC
|
||||||
func (r *Upgrader) sendHeaderedEvent(
|
func (r *Upgrader) sendHeaderedEvent(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
headeredEvent *gomatrixserverlib.HeaderedEvent,
|
headeredEvent *gomatrixserverlib.HeaderedEvent,
|
||||||
|
sendAsServer string,
|
||||||
) *api.PerformError {
|
) *api.PerformError {
|
||||||
var inputs []api.InputRoomEvent
|
var inputs []api.InputRoomEvent
|
||||||
inputs = append(inputs, api.InputRoomEvent{
|
inputs = append(inputs, api.InputRoomEvent{
|
||||||
Kind: api.KindNew,
|
Kind: api.KindNew,
|
||||||
Event: headeredEvent,
|
Event: headeredEvent,
|
||||||
Origin: r.Cfg.Matrix.ServerName,
|
Origin: r.Cfg.Matrix.ServerName,
|
||||||
SendAsServer: api.DoNotSendToOtherServers,
|
SendAsServer: sendAsServer,
|
||||||
})
|
})
|
||||||
if err := api.SendInputRoomEvents(ctx, r.URSAPI, inputs, false); err != nil {
|
if err := api.SendInputRoomEvents(ctx, r.URSAPI, inputs, false); err != nil {
|
||||||
return &api.PerformError{
|
return &api.PerformError{
|
||||||
Msg: "api.SendInputRoomEvents failed",
|
Msg: fmt.Sprintf("Failed to send new %q event to roomserver: %s", headeredEvent.Type(), err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -703,7 +704,7 @@ func (r *Upgrader) buildEvent(
|
||||||
r.Cfg.Matrix.PrivateKey, roomVersion,
|
r.Cfg.Matrix.PrivateKey, roomVersion,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot build event %s : Builder failed to build. %w", builder.Type, err)
|
return nil, err
|
||||||
}
|
}
|
||||||
return event, nil
|
return event, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -778,11 +778,18 @@ func (r *Queryer) QueryRestrictedJoinAllowed(ctx context.Context, req *api.Query
|
||||||
} else if !allowRestrictedJoins {
|
} else if !allowRestrictedJoins {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// Start off by populating the "resident" flag in the response. If we
|
||||||
|
// come across any rooms in the request that are missing, we will unset
|
||||||
|
// the flag.
|
||||||
|
res.Resident = true
|
||||||
// Get the join rules to work out if the join rule is "restricted".
|
// Get the join rules to work out if the join rule is "restricted".
|
||||||
joinRulesEvent, err := r.DB.GetStateEvent(ctx, req.RoomID, gomatrixserverlib.MRoomJoinRules, "")
|
joinRulesEvent, err := r.DB.GetStateEvent(ctx, req.RoomID, gomatrixserverlib.MRoomJoinRules, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("r.DB.GetStateEvent: %w", err)
|
return fmt.Errorf("r.DB.GetStateEvent: %w", err)
|
||||||
}
|
}
|
||||||
|
if joinRulesEvent == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
var joinRules gomatrixserverlib.JoinRuleContent
|
var joinRules gomatrixserverlib.JoinRuleContent
|
||||||
if err = json.Unmarshal(joinRulesEvent.Content(), &joinRules); err != nil {
|
if err = json.Unmarshal(joinRulesEvent.Content(), &joinRules); err != nil {
|
||||||
return fmt.Errorf("json.Unmarshal: %w", err)
|
return fmt.Errorf("json.Unmarshal: %w", err)
|
||||||
|
|
@ -792,10 +799,6 @@ func (r *Queryer) QueryRestrictedJoinAllowed(ctx context.Context, req *api.Query
|
||||||
if !res.Restricted {
|
if !res.Restricted {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Start off by populating the "resident" flag in the response. If we
|
|
||||||
// come across any rooms in the request that are missing, we will unset
|
|
||||||
// the flag.
|
|
||||||
res.Resident = true
|
|
||||||
// If the user is already invited to the room then the join is allowed
|
// If the user is already invited to the room then the join is allowed
|
||||||
// but we don't specify an authorised via user, since the event auth
|
// but we don't specify an authorised via user, since the event auth
|
||||||
// will allow the join anyway.
|
// will allow the join anyway.
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ const (
|
||||||
RoomserverPerformInboundPeekPath = "/roomserver/performInboundPeek"
|
RoomserverPerformInboundPeekPath = "/roomserver/performInboundPeek"
|
||||||
RoomserverPerformForgetPath = "/roomserver/performForget"
|
RoomserverPerformForgetPath = "/roomserver/performForget"
|
||||||
RoomserverPerformAdminEvacuateRoomPath = "/roomserver/performAdminEvacuateRoom"
|
RoomserverPerformAdminEvacuateRoomPath = "/roomserver/performAdminEvacuateRoom"
|
||||||
|
RoomserverPerformAdminEvacuateUserPath = "/roomserver/performAdminEvacuateUser"
|
||||||
|
|
||||||
// Query operations
|
// Query operations
|
||||||
RoomserverQueryLatestEventsAndStatePath = "/roomserver/queryLatestEventsAndState"
|
RoomserverQueryLatestEventsAndStatePath = "/roomserver/queryLatestEventsAndState"
|
||||||
|
|
@ -305,6 +306,23 @@ func (h *httpRoomserverInternalAPI) PerformAdminEvacuateRoom(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *httpRoomserverInternalAPI) PerformAdminEvacuateUser(
|
||||||
|
ctx context.Context,
|
||||||
|
req *api.PerformAdminEvacuateUserRequest,
|
||||||
|
res *api.PerformAdminEvacuateUserResponse,
|
||||||
|
) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "PerformAdminEvacuateUser")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
|
apiURL := h.roomserverURL + RoomserverPerformAdminEvacuateUserPath
|
||||||
|
err := httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res)
|
||||||
|
if err != nil {
|
||||||
|
res.Error = &api.PerformError{
|
||||||
|
Msg: fmt.Sprintf("failed to communicate with roomserver: %s", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// QueryLatestEventsAndState implements RoomserverQueryAPI
|
// QueryLatestEventsAndState implements RoomserverQueryAPI
|
||||||
func (h *httpRoomserverInternalAPI) QueryLatestEventsAndState(
|
func (h *httpRoomserverInternalAPI) QueryLatestEventsAndState(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,17 @@ func AddRoutes(r api.RoomserverInternalAPI, internalAPIMux *mux.Router) {
|
||||||
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
internalAPIMux.Handle(RoomserverPerformAdminEvacuateUserPath,
|
||||||
|
httputil.MakeInternalAPI("performAdminEvacuateUser", func(req *http.Request) util.JSONResponse {
|
||||||
|
var request api.PerformAdminEvacuateUserRequest
|
||||||
|
var response api.PerformAdminEvacuateUserResponse
|
||||||
|
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
|
||||||
|
return util.MessageResponse(http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
r.PerformAdminEvacuateUser(req.Context(), &request, &response)
|
||||||
|
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
||||||
|
}),
|
||||||
|
)
|
||||||
internalAPIMux.Handle(
|
internalAPIMux.Handle(
|
||||||
RoomserverQueryPublishedRoomsPath,
|
RoomserverQueryPublishedRoomsPath,
|
||||||
httputil.MakeInternalAPI("queryPublishedRooms", func(req *http.Request) util.JSONResponse {
|
httputil.MakeInternalAPI("queryPublishedRooms", func(req *http.Request) util.JSONResponse {
|
||||||
|
|
|
||||||
89
roomserver/producers/roomevent.go
Normal file
89
roomserver/producers/roomevent.go
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
// 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 producers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/acls"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
|
"github.com/nats-io/nats.go"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
var keyContentFields = map[string]string{
|
||||||
|
"m.room.join_rules": "join_rule",
|
||||||
|
"m.room.history_visibility": "history_visibility",
|
||||||
|
"m.room.member": "membership",
|
||||||
|
}
|
||||||
|
|
||||||
|
type RoomEventProducer struct {
|
||||||
|
Topic string
|
||||||
|
ACLs *acls.ServerACLs
|
||||||
|
JetStream nats.JetStreamContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RoomEventProducer) ProduceRoomEvents(roomID string, updates []api.OutputEvent) error {
|
||||||
|
var err error
|
||||||
|
for _, update := range updates {
|
||||||
|
msg := &nats.Msg{
|
||||||
|
Subject: r.Topic,
|
||||||
|
Header: nats.Header{},
|
||||||
|
}
|
||||||
|
msg.Header.Set(jetstream.RoomID, roomID)
|
||||||
|
msg.Data, err = json.Marshal(update)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger := log.WithFields(log.Fields{
|
||||||
|
"room_id": roomID,
|
||||||
|
"type": update.Type,
|
||||||
|
})
|
||||||
|
if update.NewRoomEvent != nil {
|
||||||
|
eventType := update.NewRoomEvent.Event.Type()
|
||||||
|
logger = logger.WithFields(log.Fields{
|
||||||
|
"event_type": eventType,
|
||||||
|
"event_id": update.NewRoomEvent.Event.EventID(),
|
||||||
|
"adds_state": len(update.NewRoomEvent.AddsStateEventIDs),
|
||||||
|
"removes_state": len(update.NewRoomEvent.RemovesStateEventIDs),
|
||||||
|
"send_as_server": update.NewRoomEvent.SendAsServer,
|
||||||
|
"sender": update.NewRoomEvent.Event.Sender(),
|
||||||
|
})
|
||||||
|
if update.NewRoomEvent.Event.StateKey() != nil {
|
||||||
|
logger = logger.WithField("state_key", *update.NewRoomEvent.Event.StateKey())
|
||||||
|
}
|
||||||
|
contentKey := keyContentFields[eventType]
|
||||||
|
if contentKey != "" {
|
||||||
|
value := gjson.GetBytes(update.NewRoomEvent.Event.Content(), contentKey)
|
||||||
|
if value.Exists() {
|
||||||
|
logger = logger.WithField("content_value", value.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if eventType == "m.room.server_acl" && update.NewRoomEvent.Event.StateKeyEquals("") {
|
||||||
|
ev := update.NewRoomEvent.Event.Unwrap()
|
||||||
|
defer r.ACLs.OnServerACLUpdate(ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.Tracef("Producing to topic '%s'", r.Topic)
|
||||||
|
if _, err := r.JetStream.PublishMsg(msg); err != nil {
|
||||||
|
logger.WithError(err).Errorf("Failed to produce to topic '%s': %s", r.Topic, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -55,7 +55,6 @@ func NewInternalAPI(
|
||||||
return internal.NewRoomserverAPI(
|
return internal.NewRoomserverAPI(
|
||||||
base.ProcessContext, cfg, roomserverDB, js, nc,
|
base.ProcessContext, cfg, roomserverDB, js, nc,
|
||||||
cfg.Matrix.JetStream.Prefixed(jetstream.InputRoomEvent),
|
cfg.Matrix.JetStream.Prefixed(jetstream.InputRoomEvent),
|
||||||
cfg.Matrix.JetStream.Prefixed(jetstream.OutputRoomEvent),
|
|
||||||
base.Caches, perspectiveServerNames,
|
base.Caches, perspectiveServerNames,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
"github.com/opentracing/opentracing-go"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
|
|
@ -39,6 +41,7 @@ type StateResolutionStorage interface {
|
||||||
StateAtEventIDs(ctx context.Context, eventIDs []string) ([]types.StateAtEvent, error)
|
StateAtEventIDs(ctx context.Context, eventIDs []string) ([]types.StateAtEvent, error)
|
||||||
AddState(ctx context.Context, roomNID types.RoomNID, stateBlockNIDs []types.StateBlockNID, state []types.StateEntry) (types.StateSnapshotNID, error)
|
AddState(ctx context.Context, roomNID types.RoomNID, stateBlockNIDs []types.StateBlockNID, state []types.StateEntry) (types.StateSnapshotNID, error)
|
||||||
Events(ctx context.Context, eventNIDs []types.EventNID) ([]types.Event, error)
|
Events(ctx context.Context, eventNIDs []types.EventNID) ([]types.Event, error)
|
||||||
|
EventsFromIDs(ctx context.Context, eventIDs []string) ([]types.Event, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type StateResolution struct {
|
type StateResolution struct {
|
||||||
|
|
@ -61,6 +64,9 @@ func NewStateResolution(db StateResolutionStorage, roomInfo *types.RoomInfo) Sta
|
||||||
func (v *StateResolution) LoadStateAtSnapshot(
|
func (v *StateResolution) LoadStateAtSnapshot(
|
||||||
ctx context.Context, stateNID types.StateSnapshotNID,
|
ctx context.Context, stateNID types.StateSnapshotNID,
|
||||||
) ([]types.StateEntry, error) {
|
) ([]types.StateEntry, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "StateResolution.LoadStateAtSnapshot")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
stateBlockNIDLists, err := v.db.StateBlockNIDs(ctx, []types.StateSnapshotNID{stateNID})
|
stateBlockNIDLists, err := v.db.StateBlockNIDs(ctx, []types.StateSnapshotNID{stateNID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -99,6 +105,9 @@ func (v *StateResolution) LoadStateAtSnapshot(
|
||||||
func (v *StateResolution) LoadStateAtEvent(
|
func (v *StateResolution) LoadStateAtEvent(
|
||||||
ctx context.Context, eventID string,
|
ctx context.Context, eventID string,
|
||||||
) ([]types.StateEntry, error) {
|
) ([]types.StateEntry, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "StateResolution.LoadStateAtEvent")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
snapshotNID, err := v.db.SnapshotNIDFromEventID(ctx, eventID)
|
snapshotNID, err := v.db.SnapshotNIDFromEventID(ctx, eventID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("LoadStateAtEvent.SnapshotNIDFromEventID failed for event %s : %s", eventID, err)
|
return nil, fmt.Errorf("LoadStateAtEvent.SnapshotNIDFromEventID failed for event %s : %s", eventID, err)
|
||||||
|
|
@ -121,6 +130,9 @@ func (v *StateResolution) LoadStateAtEvent(
|
||||||
func (v *StateResolution) LoadCombinedStateAfterEvents(
|
func (v *StateResolution) LoadCombinedStateAfterEvents(
|
||||||
ctx context.Context, prevStates []types.StateAtEvent,
|
ctx context.Context, prevStates []types.StateAtEvent,
|
||||||
) ([]types.StateEntry, error) {
|
) ([]types.StateEntry, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "StateResolution.LoadCombinedStateAfterEvents")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
stateNIDs := make([]types.StateSnapshotNID, len(prevStates))
|
stateNIDs := make([]types.StateSnapshotNID, len(prevStates))
|
||||||
for i, state := range prevStates {
|
for i, state := range prevStates {
|
||||||
stateNIDs[i] = state.BeforeStateSnapshotNID
|
stateNIDs[i] = state.BeforeStateSnapshotNID
|
||||||
|
|
@ -193,6 +205,9 @@ func (v *StateResolution) LoadCombinedStateAfterEvents(
|
||||||
func (v *StateResolution) DifferenceBetweeenStateSnapshots(
|
func (v *StateResolution) DifferenceBetweeenStateSnapshots(
|
||||||
ctx context.Context, oldStateNID, newStateNID types.StateSnapshotNID,
|
ctx context.Context, oldStateNID, newStateNID types.StateSnapshotNID,
|
||||||
) (removed, added []types.StateEntry, err error) {
|
) (removed, added []types.StateEntry, err error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "StateResolution.DifferenceBetweeenStateSnapshots")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
if oldStateNID == newStateNID {
|
if oldStateNID == newStateNID {
|
||||||
// If the snapshot NIDs are the same then nothing has changed
|
// If the snapshot NIDs are the same then nothing has changed
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
|
|
@ -254,6 +269,9 @@ func (v *StateResolution) LoadStateAtSnapshotForStringTuples(
|
||||||
stateNID types.StateSnapshotNID,
|
stateNID types.StateSnapshotNID,
|
||||||
stateKeyTuples []gomatrixserverlib.StateKeyTuple,
|
stateKeyTuples []gomatrixserverlib.StateKeyTuple,
|
||||||
) ([]types.StateEntry, error) {
|
) ([]types.StateEntry, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "StateResolution.LoadStateAtSnapshotForStringTuples")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
numericTuples, err := v.stringTuplesToNumericTuples(ctx, stateKeyTuples)
|
numericTuples, err := v.stringTuplesToNumericTuples(ctx, stateKeyTuples)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -268,6 +286,9 @@ func (v *StateResolution) stringTuplesToNumericTuples(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
stringTuples []gomatrixserverlib.StateKeyTuple,
|
stringTuples []gomatrixserverlib.StateKeyTuple,
|
||||||
) ([]types.StateKeyTuple, error) {
|
) ([]types.StateKeyTuple, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "StateResolution.stringTuplesToNumericTuples")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
eventTypes := make([]string, len(stringTuples))
|
eventTypes := make([]string, len(stringTuples))
|
||||||
stateKeys := make([]string, len(stringTuples))
|
stateKeys := make([]string, len(stringTuples))
|
||||||
for i := range stringTuples {
|
for i := range stringTuples {
|
||||||
|
|
@ -310,6 +331,9 @@ func (v *StateResolution) loadStateAtSnapshotForNumericTuples(
|
||||||
stateNID types.StateSnapshotNID,
|
stateNID types.StateSnapshotNID,
|
||||||
stateKeyTuples []types.StateKeyTuple,
|
stateKeyTuples []types.StateKeyTuple,
|
||||||
) ([]types.StateEntry, error) {
|
) ([]types.StateEntry, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "StateResolution.loadStateAtSnapshotForNumericTuples")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
stateBlockNIDLists, err := v.db.StateBlockNIDs(ctx, []types.StateSnapshotNID{stateNID})
|
stateBlockNIDLists, err := v.db.StateBlockNIDs(ctx, []types.StateSnapshotNID{stateNID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -358,6 +382,9 @@ func (v *StateResolution) LoadStateAfterEventsForStringTuples(
|
||||||
prevStates []types.StateAtEvent,
|
prevStates []types.StateAtEvent,
|
||||||
stateKeyTuples []gomatrixserverlib.StateKeyTuple,
|
stateKeyTuples []gomatrixserverlib.StateKeyTuple,
|
||||||
) ([]types.StateEntry, error) {
|
) ([]types.StateEntry, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "StateResolution.LoadStateAfterEventsForStringTuples")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
numericTuples, err := v.stringTuplesToNumericTuples(ctx, stateKeyTuples)
|
numericTuples, err := v.stringTuplesToNumericTuples(ctx, stateKeyTuples)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -370,6 +397,9 @@ func (v *StateResolution) loadStateAfterEventsForNumericTuples(
|
||||||
prevStates []types.StateAtEvent,
|
prevStates []types.StateAtEvent,
|
||||||
stateKeyTuples []types.StateKeyTuple,
|
stateKeyTuples []types.StateKeyTuple,
|
||||||
) ([]types.StateEntry, error) {
|
) ([]types.StateEntry, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "StateResolution.loadStateAfterEventsForNumericTuples")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
if len(prevStates) == 1 {
|
if len(prevStates) == 1 {
|
||||||
// Fast path for a single event.
|
// Fast path for a single event.
|
||||||
prevState := prevStates[0]
|
prevState := prevStates[0]
|
||||||
|
|
@ -542,6 +572,9 @@ func (v *StateResolution) CalculateAndStoreStateBeforeEvent(
|
||||||
event *gomatrixserverlib.Event,
|
event *gomatrixserverlib.Event,
|
||||||
isRejected bool,
|
isRejected bool,
|
||||||
) (types.StateSnapshotNID, error) {
|
) (types.StateSnapshotNID, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "StateResolution.CalculateAndStoreStateBeforeEvent")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
// Load the state at the prev events.
|
// Load the state at the prev events.
|
||||||
prevStates, err := v.db.StateAtEventIDs(ctx, event.PrevEventIDs())
|
prevStates, err := v.db.StateAtEventIDs(ctx, event.PrevEventIDs())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -558,6 +591,9 @@ func (v *StateResolution) CalculateAndStoreStateAfterEvents(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
prevStates []types.StateAtEvent,
|
prevStates []types.StateAtEvent,
|
||||||
) (types.StateSnapshotNID, error) {
|
) (types.StateSnapshotNID, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "StateResolution.CalculateAndStoreStateAfterEvents")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
metrics := calculateStateMetrics{startTime: time.Now(), prevEventLength: len(prevStates)}
|
metrics := calculateStateMetrics{startTime: time.Now(), prevEventLength: len(prevStates)}
|
||||||
|
|
||||||
if len(prevStates) == 0 {
|
if len(prevStates) == 0 {
|
||||||
|
|
@ -630,6 +666,9 @@ func (v *StateResolution) calculateAndStoreStateAfterManyEvents(
|
||||||
prevStates []types.StateAtEvent,
|
prevStates []types.StateAtEvent,
|
||||||
metrics calculateStateMetrics,
|
metrics calculateStateMetrics,
|
||||||
) (types.StateSnapshotNID, error) {
|
) (types.StateSnapshotNID, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "StateResolution.calculateAndStoreStateAfterManyEvents")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
state, algorithm, conflictLength, err :=
|
state, algorithm, conflictLength, err :=
|
||||||
v.calculateStateAfterManyEvents(ctx, v.roomInfo.RoomVersion, prevStates)
|
v.calculateStateAfterManyEvents(ctx, v.roomInfo.RoomVersion, prevStates)
|
||||||
metrics.algorithm = algorithm
|
metrics.algorithm = algorithm
|
||||||
|
|
@ -648,6 +687,9 @@ func (v *StateResolution) calculateStateAfterManyEvents(
|
||||||
ctx context.Context, roomVersion gomatrixserverlib.RoomVersion,
|
ctx context.Context, roomVersion gomatrixserverlib.RoomVersion,
|
||||||
prevStates []types.StateAtEvent,
|
prevStates []types.StateAtEvent,
|
||||||
) (state []types.StateEntry, algorithm string, conflictLength int, err error) {
|
) (state []types.StateEntry, algorithm string, conflictLength int, err error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "StateResolution.calculateStateAfterManyEvents")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
var combined []types.StateEntry
|
var combined []types.StateEntry
|
||||||
// Conflict resolution.
|
// Conflict resolution.
|
||||||
// First stage: load the state after each of the prev events.
|
// First stage: load the state after each of the prev events.
|
||||||
|
|
@ -659,15 +701,13 @@ func (v *StateResolution) calculateStateAfterManyEvents(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect all the entries with the same type and key together.
|
// Collect all the entries with the same type and key together.
|
||||||
// We don't care about the order here because the conflict resolution
|
// This is done so findDuplicateStateKeys can work in groups.
|
||||||
// algorithm doesn't depend on the order of the prev events.
|
// We remove duplicates (same type, state key and event NID) too.
|
||||||
// Remove duplicate entires.
|
|
||||||
combined = combined[:util.SortAndUnique(stateEntrySorter(combined))]
|
combined = combined[:util.SortAndUnique(stateEntrySorter(combined))]
|
||||||
|
|
||||||
// Find the conflicts
|
// Find the conflicts
|
||||||
conflicts := findDuplicateStateKeys(combined)
|
if conflicts := findDuplicateStateKeys(combined); len(conflicts) > 0 {
|
||||||
|
conflictMap := stateEntryMap(conflicts)
|
||||||
if len(conflicts) > 0 {
|
|
||||||
conflictLength = len(conflicts)
|
conflictLength = len(conflicts)
|
||||||
|
|
||||||
// 5) There are conflicting state events, for each conflict workout
|
// 5) There are conflicting state events, for each conflict workout
|
||||||
|
|
@ -676,7 +716,7 @@ func (v *StateResolution) calculateStateAfterManyEvents(
|
||||||
// Work out which entries aren't conflicted.
|
// Work out which entries aren't conflicted.
|
||||||
var notConflicted []types.StateEntry
|
var notConflicted []types.StateEntry
|
||||||
for _, entry := range combined {
|
for _, entry := range combined {
|
||||||
if _, ok := stateEntryMap(conflicts).lookup(entry.StateKeyTuple); !ok {
|
if _, ok := conflictMap.lookup(entry.StateKeyTuple); !ok {
|
||||||
notConflicted = append(notConflicted, entry)
|
notConflicted = append(notConflicted, entry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -689,7 +729,7 @@ func (v *StateResolution) calculateStateAfterManyEvents(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
algorithm = "full_state_with_conflicts"
|
algorithm = "full_state_with_conflicts"
|
||||||
state = resolved[:util.SortAndUnique(stateEntrySorter(resolved))]
|
state = resolved
|
||||||
} else {
|
} else {
|
||||||
algorithm = "full_state_no_conflicts"
|
algorithm = "full_state_no_conflicts"
|
||||||
// 6) There weren't any conflicts
|
// 6) There weren't any conflicts
|
||||||
|
|
@ -702,6 +742,9 @@ func (v *StateResolution) resolveConflicts(
|
||||||
ctx context.Context, version gomatrixserverlib.RoomVersion,
|
ctx context.Context, version gomatrixserverlib.RoomVersion,
|
||||||
notConflicted, conflicted []types.StateEntry,
|
notConflicted, conflicted []types.StateEntry,
|
||||||
) ([]types.StateEntry, error) {
|
) ([]types.StateEntry, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "StateResolution.resolveConflicts")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
stateResAlgo, err := version.StateResAlgorithm()
|
stateResAlgo, err := version.StateResAlgorithm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -726,6 +769,8 @@ func (v *StateResolution) resolveConflictsV1(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
notConflicted, conflicted []types.StateEntry,
|
notConflicted, conflicted []types.StateEntry,
|
||||||
) ([]types.StateEntry, error) {
|
) ([]types.StateEntry, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "StateResolution.resolveConflictsV1")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
// Load the conflicted events
|
// Load the conflicted events
|
||||||
conflictedEvents, eventIDMap, err := v.loadStateEvents(ctx, conflicted)
|
conflictedEvents, eventIDMap, err := v.loadStateEvents(ctx, conflicted)
|
||||||
|
|
@ -789,6 +834,9 @@ func (v *StateResolution) resolveConflictsV2(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
notConflicted, conflicted []types.StateEntry,
|
notConflicted, conflicted []types.StateEntry,
|
||||||
) ([]types.StateEntry, error) {
|
) ([]types.StateEntry, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "StateResolution.resolveConflictsV2")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
estimate := len(conflicted) + len(notConflicted)
|
estimate := len(conflicted) + len(notConflicted)
|
||||||
eventIDMap := make(map[string]types.StateEntry, estimate)
|
eventIDMap := make(map[string]types.StateEntry, estimate)
|
||||||
|
|
||||||
|
|
@ -816,51 +864,47 @@ func (v *StateResolution) resolveConflictsV2(
|
||||||
authEvents := make([]*gomatrixserverlib.Event, 0, estimate*3)
|
authEvents := make([]*gomatrixserverlib.Event, 0, estimate*3)
|
||||||
gotAuthEvents := make(map[string]struct{}, estimate*3)
|
gotAuthEvents := make(map[string]struct{}, estimate*3)
|
||||||
authDifference := make([]*gomatrixserverlib.Event, 0, estimate)
|
authDifference := make([]*gomatrixserverlib.Event, 0, estimate)
|
||||||
|
knownAuthEvents := make(map[string]types.Event, estimate*3)
|
||||||
|
|
||||||
// For each conflicted event, let's try and get the needed auth events.
|
// For each conflicted event, let's try and get the needed auth events.
|
||||||
neededStateKeys := make([]string, 16)
|
if err = func() error {
|
||||||
authEntries := make([]types.StateEntry, 16)
|
span, sctx := opentracing.StartSpanFromContext(ctx, "StateResolution.loadAuthEvents")
|
||||||
for _, conflictedEvent := range conflictedEvents {
|
defer span.Finish()
|
||||||
// Work out which auth events we need to load.
|
|
||||||
key := conflictedEvent.EventID()
|
|
||||||
needed := gomatrixserverlib.StateNeededForAuth([]*gomatrixserverlib.Event{conflictedEvent})
|
|
||||||
|
|
||||||
// Find the numeric IDs for the necessary state keys.
|
loader := authEventLoader{
|
||||||
neededStateKeys = neededStateKeys[:0]
|
v: v,
|
||||||
neededStateKeys = append(neededStateKeys, needed.Member...)
|
lookupFromDB: make([]string, 0, len(conflictedEvents)*3),
|
||||||
neededStateKeys = append(neededStateKeys, needed.ThirdPartyInvite...)
|
lookupFromMem: make([]string, 0, len(conflictedEvents)*3),
|
||||||
stateKeyNIDMap, err := v.db.EventStateKeyNIDs(ctx, neededStateKeys)
|
lookedUpEvents: make([]types.Event, 0, len(conflictedEvents)*3),
|
||||||
if err != nil {
|
eventMap: map[string]types.Event{},
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
for _, conflictedEvent := range conflictedEvents {
|
||||||
|
// Work out which auth events we need to load.
|
||||||
|
key := conflictedEvent.EventID()
|
||||||
|
|
||||||
// Load the necessary auth events.
|
// Store the newly found auth events in the auth set for this event.
|
||||||
tuplesNeeded := v.stateKeyTuplesNeeded(stateKeyNIDMap, needed)
|
var authEventMap map[string]types.StateEntry
|
||||||
authEntries = authEntries[:0]
|
authSets[key], authEventMap, err = loader.loadAuthEvents(sctx, conflictedEvent, knownAuthEvents)
|
||||||
for _, tuple := range tuplesNeeded {
|
if err != nil {
|
||||||
if eventNID, ok := stateEntryMap(notConflicted).lookup(tuple); ok {
|
return err
|
||||||
authEntries = append(authEntries, types.StateEntry{
|
}
|
||||||
StateKeyTuple: tuple,
|
for k, v := range authEventMap {
|
||||||
EventNID: eventNID,
|
eventIDMap[k] = v
|
||||||
})
|
}
|
||||||
}
|
|
||||||
}
|
// Only add auth events into the authEvents slice once, otherwise the
|
||||||
|
// check for the auth difference can become expensive and produce
|
||||||
// Store the newly found auth events in the auth set for this event.
|
// duplicate entries, which just waste memory and CPU time.
|
||||||
authSets[key], _, err = v.loadStateEvents(ctx, authEntries)
|
for _, event := range authSets[key] {
|
||||||
if err != nil {
|
if _, ok := gotAuthEvents[event.EventID()]; !ok {
|
||||||
return nil, err
|
authEvents = append(authEvents, event)
|
||||||
}
|
gotAuthEvents[event.EventID()] = struct{}{}
|
||||||
|
}
|
||||||
// Only add auth events into the authEvents slice once, otherwise the
|
|
||||||
// check for the auth difference can become expensive and produce
|
|
||||||
// duplicate entries, which just waste memory and CPU time.
|
|
||||||
for _, event := range authSets[key] {
|
|
||||||
if _, ok := gotAuthEvents[event.EventID()]; !ok {
|
|
||||||
authEvents = append(authEvents, event)
|
|
||||||
gotAuthEvents[event.EventID()] = struct{}{}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}(); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kill the reference to this so that the GC may pick it up, since we no
|
// Kill the reference to this so that the GC may pick it up, since we no
|
||||||
|
|
@ -891,25 +935,35 @@ func (v *StateResolution) resolveConflictsV2(
|
||||||
// Look through all of the auth events that we've been given and work out if
|
// Look through all of the auth events that we've been given and work out if
|
||||||
// there are any events which don't appear in all of the auth sets. If they
|
// there are any events which don't appear in all of the auth sets. If they
|
||||||
// don't then we add them to the auth difference.
|
// don't then we add them to the auth difference.
|
||||||
for _, event := range authEvents {
|
func() {
|
||||||
if !isInAllAuthLists(event) {
|
span, _ := opentracing.StartSpanFromContext(ctx, "isInAllAuthLists")
|
||||||
authDifference = append(authDifference, event)
|
defer span.Finish()
|
||||||
|
|
||||||
|
for _, event := range authEvents {
|
||||||
|
if !isInAllAuthLists(event) {
|
||||||
|
authDifference = append(authDifference, event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}()
|
||||||
|
|
||||||
// Resolve the conflicts.
|
// Resolve the conflicts.
|
||||||
resolvedEvents := gomatrixserverlib.ResolveStateConflictsV2(
|
resolvedEvents := func() []*gomatrixserverlib.Event {
|
||||||
conflictedEvents,
|
span, _ := opentracing.StartSpanFromContext(ctx, "gomatrixserverlib.ResolveStateConflictsV2")
|
||||||
nonConflictedEvents,
|
defer span.Finish()
|
||||||
authEvents,
|
|
||||||
authDifference,
|
return gomatrixserverlib.ResolveStateConflictsV2(
|
||||||
)
|
conflictedEvents,
|
||||||
|
nonConflictedEvents,
|
||||||
|
authEvents,
|
||||||
|
authDifference,
|
||||||
|
)
|
||||||
|
}()
|
||||||
|
|
||||||
// Map from the full events back to numeric state entries.
|
// Map from the full events back to numeric state entries.
|
||||||
for _, resolvedEvent := range resolvedEvents {
|
for _, resolvedEvent := range resolvedEvents {
|
||||||
entry, ok := eventIDMap[resolvedEvent.EventID()]
|
entry, ok := eventIDMap[resolvedEvent.EventID()]
|
||||||
if !ok {
|
if !ok {
|
||||||
panic(fmt.Errorf("missing state entry for event ID %q", resolvedEvent.EventID()))
|
return nil, fmt.Errorf("missing state entry for event ID %q", resolvedEvent.EventID())
|
||||||
}
|
}
|
||||||
notConflicted = append(notConflicted, entry)
|
notConflicted = append(notConflicted, entry)
|
||||||
}
|
}
|
||||||
|
|
@ -968,6 +1022,9 @@ func (v *StateResolution) stateKeyTuplesNeeded(stateKeyNIDMap map[string]types.E
|
||||||
func (v *StateResolution) loadStateEvents(
|
func (v *StateResolution) loadStateEvents(
|
||||||
ctx context.Context, entries []types.StateEntry,
|
ctx context.Context, entries []types.StateEntry,
|
||||||
) ([]*gomatrixserverlib.Event, map[string]types.StateEntry, error) {
|
) ([]*gomatrixserverlib.Event, map[string]types.StateEntry, error) {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "StateResolution.loadStateEvents")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
result := make([]*gomatrixserverlib.Event, 0, len(entries))
|
result := make([]*gomatrixserverlib.Event, 0, len(entries))
|
||||||
eventEntries := make([]types.StateEntry, 0, len(entries))
|
eventEntries := make([]types.StateEntry, 0, len(entries))
|
||||||
eventNIDs := make([]types.EventNID, 0, len(entries))
|
eventNIDs := make([]types.EventNID, 0, len(entries))
|
||||||
|
|
@ -996,6 +1053,127 @@ func (v *StateResolution) loadStateEvents(
|
||||||
return result, eventIDMap, nil
|
return result, eventIDMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type authEventLoader struct {
|
||||||
|
sync.Mutex
|
||||||
|
v *StateResolution
|
||||||
|
lookupFromDB []string // scratch space
|
||||||
|
lookupFromMem []string // scratch space
|
||||||
|
lookedUpEvents []types.Event // scratch space
|
||||||
|
eventMap map[string]types.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadAuthEvents loads all of the auth events for a given event recursively,
|
||||||
|
// along with a map that contains state entries for all of the auth events.
|
||||||
|
func (l *authEventLoader) loadAuthEvents(
|
||||||
|
ctx context.Context, event *gomatrixserverlib.Event, eventMap map[string]types.Event,
|
||||||
|
) ([]*gomatrixserverlib.Event, map[string]types.StateEntry, error) {
|
||||||
|
l.Lock()
|
||||||
|
defer l.Unlock()
|
||||||
|
authEvents := []types.Event{} // our returned list
|
||||||
|
included := map[string]struct{}{} // dedupes authEvents above
|
||||||
|
queue := event.AuthEventIDs()
|
||||||
|
for i := 0; i < len(queue); i++ {
|
||||||
|
// Reuse the same underlying memory, since it reduces the
|
||||||
|
// amount of allocations we make the more times we call
|
||||||
|
// loadAuthEvents.
|
||||||
|
l.lookupFromDB = l.lookupFromDB[:0]
|
||||||
|
l.lookupFromMem = l.lookupFromMem[:0]
|
||||||
|
l.lookedUpEvents = l.lookedUpEvents[:0]
|
||||||
|
|
||||||
|
// Separate out the list of events in the queue based on if
|
||||||
|
// we think we already know the event in memory or not.
|
||||||
|
for _, authEventID := range queue {
|
||||||
|
if _, ok := included[authEventID]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := eventMap[authEventID]; ok {
|
||||||
|
l.lookupFromMem = append(l.lookupFromMem, authEventID)
|
||||||
|
} else {
|
||||||
|
l.lookupFromDB = append(l.lookupFromDB, authEventID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If there's nothing to do, stop here.
|
||||||
|
if len(l.lookupFromDB) == 0 && len(l.lookupFromMem) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we need to get events from the database, go and fetch
|
||||||
|
// those now.
|
||||||
|
if len(l.lookupFromDB) > 0 {
|
||||||
|
eventsFromDB, err := l.v.db.EventsFromIDs(ctx, l.lookupFromDB)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("v.db.EventsFromIDs: %w", err)
|
||||||
|
}
|
||||||
|
l.lookedUpEvents = append(l.lookedUpEvents, eventsFromDB...)
|
||||||
|
for _, event := range eventsFromDB {
|
||||||
|
eventMap[event.EventID()] = event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in the gaps with events that we already have in memory.
|
||||||
|
if len(l.lookupFromMem) > 0 {
|
||||||
|
for _, eventID := range l.lookupFromMem {
|
||||||
|
l.lookedUpEvents = append(l.lookedUpEvents, eventMap[eventID])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// From the events that we've retrieved, work out which auth
|
||||||
|
// events to look up on the next iteration.
|
||||||
|
add := map[string]struct{}{}
|
||||||
|
for _, event := range l.lookedUpEvents {
|
||||||
|
authEvents = append(authEvents, event)
|
||||||
|
included[event.EventID()] = struct{}{}
|
||||||
|
|
||||||
|
for _, authEventID := range event.AuthEventIDs() {
|
||||||
|
if _, ok := included[authEventID]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
add[authEventID] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for authEventID := range add {
|
||||||
|
queue = append(queue, authEventID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authEventTypes := map[string]struct{}{}
|
||||||
|
authEventStateKeys := map[string]struct{}{}
|
||||||
|
for _, authEvent := range authEvents {
|
||||||
|
authEventTypes[authEvent.Type()] = struct{}{}
|
||||||
|
authEventStateKeys[*authEvent.StateKey()] = struct{}{}
|
||||||
|
}
|
||||||
|
lookupAuthEventTypes := make([]string, 0, len(authEventTypes))
|
||||||
|
lookupAuthEventStateKeys := make([]string, 0, len(authEventStateKeys))
|
||||||
|
for eventType := range authEventTypes {
|
||||||
|
lookupAuthEventTypes = append(lookupAuthEventTypes, eventType)
|
||||||
|
}
|
||||||
|
for eventStateKey := range authEventStateKeys {
|
||||||
|
lookupAuthEventStateKeys = append(lookupAuthEventStateKeys, eventStateKey)
|
||||||
|
}
|
||||||
|
eventTypes, err := l.v.db.EventTypeNIDs(ctx, lookupAuthEventTypes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("v.db.EventTypeNIDs: %w", err)
|
||||||
|
}
|
||||||
|
eventStateKeys, err := l.v.db.EventStateKeyNIDs(ctx, lookupAuthEventStateKeys)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("v.db.EventStateKeyNIDs: %w", err)
|
||||||
|
}
|
||||||
|
stateEntryMap := map[string]types.StateEntry{}
|
||||||
|
for _, authEvent := range authEvents {
|
||||||
|
stateEntryMap[authEvent.EventID()] = types.StateEntry{
|
||||||
|
EventNID: authEvent.EventNID,
|
||||||
|
StateKeyTuple: types.StateKeyTuple{
|
||||||
|
EventTypeNID: eventTypes[authEvent.Type()],
|
||||||
|
EventStateKeyNID: eventStateKeys[*authEvent.StateKey()],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nakedEvents := make([]*gomatrixserverlib.Event, 0, len(authEvents))
|
||||||
|
for _, authEvent := range authEvents {
|
||||||
|
nakedEvents = append(nakedEvents, authEvent.Event)
|
||||||
|
}
|
||||||
|
return nakedEvents, stateEntryMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
// findDuplicateStateKeys finds the state entries where the state key tuple appears more than once in a sorted list.
|
// findDuplicateStateKeys finds the state entries where the state key tuple appears more than once in a sorted list.
|
||||||
// Returns a sorted list of those state entries.
|
// Returns a sorted list of those state entries.
|
||||||
func findDuplicateStateKeys(a []types.StateEntry) []types.StateEntry {
|
func findDuplicateStateKeys(a []types.StateEntry) []types.StateEntry {
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,10 @@ func (u *RoomUpdater) StateAtEventIDs(
|
||||||
return u.d.EventsTable.BulkSelectStateAtEventByID(ctx, u.txn, eventIDs)
|
return u.d.EventsTable.BulkSelectStateAtEventByID(ctx, u.txn, eventIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *RoomUpdater) EventsFromIDs(ctx context.Context, eventIDs []string) ([]types.Event, error) {
|
||||||
|
return u.d.eventsFromIDs(ctx, u.txn, eventIDs, false)
|
||||||
|
}
|
||||||
|
|
||||||
func (u *RoomUpdater) UnsentEventsFromIDs(ctx context.Context, eventIDs []string) ([]types.Event, error) {
|
func (u *RoomUpdater) UnsentEventsFromIDs(ctx context.Context, eventIDs []string) ([]types.Event, error) {
|
||||||
return u.d.eventsFromIDs(ctx, u.txn, eventIDs, true)
|
return u.d.eventsFromIDs(ctx, u.txn, eventIDs, true)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -823,13 +823,39 @@ func (d *Database) handleRedactions(
|
||||||
return nil, "", nil
|
return nil, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the power level from the database, so we can verify the user is allowed to redact the event
|
||||||
|
powerLevels, err := d.GetStateEvent(ctx, event.RoomID(), gomatrixserverlib.MRoomPowerLevels, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("d.GetStateEvent: %w", err)
|
||||||
|
}
|
||||||
|
pl, err := powerLevels.PowerLevels()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("unable to get powerlevels for room: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
redactUser := pl.UserLevel(redactionEvent.Sender())
|
||||||
|
switch {
|
||||||
|
case redactUser >= pl.Redact:
|
||||||
|
// The power level of the redaction event’s sender is greater than or equal to the redact level.
|
||||||
|
case redactedEvent.Origin() == redactionEvent.Origin() && redactedEvent.Sender() == redactionEvent.Sender():
|
||||||
|
// The domain of the redaction event’s sender matches that of the original event’s sender.
|
||||||
|
default:
|
||||||
|
return nil, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
// mark the event as redacted
|
// mark the event as redacted
|
||||||
|
if redactionsArePermanent {
|
||||||
|
redactedEvent.Event = redactedEvent.Redact()
|
||||||
|
}
|
||||||
|
|
||||||
err = redactedEvent.SetUnsignedField("redacted_because", redactionEvent)
|
err = redactedEvent.SetUnsignedField("redacted_because", redactionEvent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("redactedEvent.SetUnsignedField: %w", err)
|
return nil, "", fmt.Errorf("redactedEvent.SetUnsignedField: %w", err)
|
||||||
}
|
}
|
||||||
if redactionsArePermanent {
|
// NOTSPEC: sytest relies on this unspecced field existing :(
|
||||||
redactedEvent.Event = redactedEvent.Redact()
|
err = redactedEvent.SetUnsignedField("redacted_by", redactionEvent.EventID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("redactedEvent.SetUnsignedField: %w", err)
|
||||||
}
|
}
|
||||||
// overwrite the eventJSON table
|
// overwrite the eventJSON table
|
||||||
err = d.EventJSONTable.InsertEventJSON(ctx, txn, redactedEvent.EventNID, redactedEvent.JSON())
|
err = d.EventJSONTable.InsertEventJSON(ctx, txn, redactedEvent.EventNID, redactedEvent.JSON())
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,21 @@ func platformSanityChecks() {
|
||||||
// If we run out of file descriptors, we might run into problems accessing
|
// If we run out of file descriptors, we might run into problems accessing
|
||||||
// PostgreSQL amongst other things. Complain at startup if we think the
|
// PostgreSQL amongst other things. Complain at startup if we think the
|
||||||
// number of file descriptors is too low.
|
// number of file descriptors is too low.
|
||||||
var rLimit syscall.Rlimit
|
warn := func(rLimit *syscall.Rlimit) {
|
||||||
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err == nil && rLimit.Cur < 65535 {
|
|
||||||
logrus.Warnf("IMPORTANT: Process file descriptor limit is currently %d, it is recommended to raise the limit for Dendrite to at least 65535 to avoid issues", rLimit.Cur)
|
logrus.Warnf("IMPORTANT: Process file descriptor limit is currently %d, it is recommended to raise the limit for Dendrite to at least 65535 to avoid issues", rLimit.Cur)
|
||||||
}
|
}
|
||||||
|
var rLimit syscall.Rlimit
|
||||||
|
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err == nil && rLimit.Cur < 65535 {
|
||||||
|
// The file descriptor count is too low. Let's try to raise it.
|
||||||
|
rLimit.Cur = 65535
|
||||||
|
if err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
|
||||||
|
// We failed to raise it, so log an error.
|
||||||
|
logrus.WithError(err).Warn("IMPORTANT: Failed to raise the file descriptor limit")
|
||||||
|
warn(&rLimit)
|
||||||
|
} else if err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err == nil && rLimit.Cur < 65535 {
|
||||||
|
// We think we successfully raised the limit, but a second call to
|
||||||
|
// get the limit told us that we didn't succeed. Log an error.
|
||||||
|
warn(&rLimit)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,10 @@ type RateLimiting struct {
|
||||||
// The cooloff period in milliseconds after a request before the "slot"
|
// The cooloff period in milliseconds after a request before the "slot"
|
||||||
// is freed again
|
// is freed again
|
||||||
CooloffMS int64 `yaml:"cooloff_ms"`
|
CooloffMS int64 `yaml:"cooloff_ms"`
|
||||||
|
|
||||||
|
// A list of users that are exempt from rate limiting, i.e. if you want
|
||||||
|
// to run Mjolnir or other bots.
|
||||||
|
ExemptUserIDs []string `yaml:"exempt_user_ids"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RateLimiting) Verify(configErrs *ConfigErrors) {
|
func (r *RateLimiting) Verify(configErrs *ConfigErrors) {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ const (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
InputRoomEvent = "InputRoomEvent"
|
InputRoomEvent = "InputRoomEvent"
|
||||||
|
InputDeviceListUpdate = "InputDeviceListUpdate"
|
||||||
OutputRoomEvent = "OutputRoomEvent"
|
OutputRoomEvent = "OutputRoomEvent"
|
||||||
OutputSendToDeviceEvent = "OutputSendToDeviceEvent"
|
OutputSendToDeviceEvent = "OutputSendToDeviceEvent"
|
||||||
OutputKeyChangeEvent = "OutputKeyChangeEvent"
|
OutputKeyChangeEvent = "OutputKeyChangeEvent"
|
||||||
|
|
@ -46,6 +47,11 @@ var streams = []*nats.StreamConfig{
|
||||||
Retention: nats.InterestPolicy,
|
Retention: nats.InterestPolicy,
|
||||||
Storage: nats.FileStorage,
|
Storage: nats.FileStorage,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: InputDeviceListUpdate,
|
||||||
|
Retention: nats.InterestPolicy,
|
||||||
|
Storage: nats.FileStorage,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: OutputRoomEvent,
|
Name: OutputRoomEvent,
|
||||||
Retention: nats.InterestPolicy,
|
Retention: nats.InterestPolicy,
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@ func (s *PresenceConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||||
presence := msg.Header.Get("presence")
|
presence := msg.Header.Get("presence")
|
||||||
timestamp := msg.Header.Get("last_active_ts")
|
timestamp := msg.Header.Get("last_active_ts")
|
||||||
fromSync, _ := strconv.ParseBool(msg.Header.Get("from_sync"))
|
fromSync, _ := strconv.ParseBool(msg.Header.Get("from_sync"))
|
||||||
logrus.Debugf("syncAPI received presence event: %+v", msg.Header)
|
logrus.Tracef("syncAPI received presence event: %+v", msg.Header)
|
||||||
|
|
||||||
if fromSync { // do not process local presence changes; we already did this synchronously.
|
if fromSync { // do not process local presence changes; we already did this synchronously.
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,6 @@ func (k *mockKeyAPI) QueryOneTimeKeys(ctx context.Context, req *keyapi.QueryOneT
|
||||||
}
|
}
|
||||||
func (k *mockKeyAPI) QueryDeviceMessages(ctx context.Context, req *keyapi.QueryDeviceMessagesRequest, res *keyapi.QueryDeviceMessagesResponse) {
|
func (k *mockKeyAPI) QueryDeviceMessages(ctx context.Context, req *keyapi.QueryDeviceMessagesRequest, res *keyapi.QueryDeviceMessagesResponse) {
|
||||||
|
|
||||||
}
|
|
||||||
func (k *mockKeyAPI) InputDeviceListUpdate(ctx context.Context, req *keyapi.InputDeviceListUpdateRequest, res *keyapi.InputDeviceListUpdateResponse) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
func (k *mockKeyAPI) QuerySignatures(ctx context.Context, req *keyapi.QuerySignaturesRequest, res *keyapi.QuerySignaturesResponse) {
|
func (k *mockKeyAPI) QuerySignatures(ctx context.Context, req *keyapi.QuerySignaturesRequest, res *keyapi.QuerySignaturesResponse) {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
package routing
|
package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -25,6 +26,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/internal/caching"
|
"github.com/matrix-org/dendrite/internal/caching"
|
||||||
roomserver "github.com/matrix-org/dendrite/roomserver/api"
|
roomserver "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/syncapi/storage"
|
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||||
|
"github.com/matrix-org/dendrite/syncapi/types"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
@ -97,7 +99,7 @@ func Context(
|
||||||
state, _ := syncDB.CurrentState(ctx, roomID, &stateFilter, nil)
|
state, _ := syncDB.CurrentState(ctx, roomID, &stateFilter, nil)
|
||||||
// verify the user is allowed to see the context for this room/event
|
// verify the user is allowed to see the context for this room/event
|
||||||
for _, x := range state {
|
for _, x := range state {
|
||||||
var hisVis string
|
var hisVis gomatrixserverlib.HistoryVisibility
|
||||||
hisVis, err = x.HistoryVisibility()
|
hisVis, err = x.HistoryVisibility()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
|
|
@ -150,13 +152,30 @@ func Context(
|
||||||
if len(response.State) > filter.Limit {
|
if len(response.State) > filter.Limit {
|
||||||
response.State = response.State[len(response.State)-filter.Limit:]
|
response.State = response.State[len(response.State)-filter.Limit:]
|
||||||
}
|
}
|
||||||
|
start, end, err := getStartEnd(ctx, syncDB, eventsBefore, eventsAfter)
|
||||||
|
if err == nil {
|
||||||
|
response.End = end.String()
|
||||||
|
response.Start = start.String()
|
||||||
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
JSON: response,
|
JSON: response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getStartEnd(ctx context.Context, syncDB storage.Database, startEvents, endEvents []*gomatrixserverlib.HeaderedEvent) (start, end types.TopologyToken, err error) {
|
||||||
|
if len(startEvents) > 0 {
|
||||||
|
start, err = syncDB.EventPositionInTopology(ctx, startEvents[0].EventID())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(endEvents) > 0 {
|
||||||
|
end, err = syncDB.EventPositionInTopology(ctx, endEvents[0].EventID())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func applyLazyLoadMembers(
|
func applyLazyLoadMembers(
|
||||||
device *userapi.Device,
|
device *userapi.Device,
|
||||||
filter *gomatrixserverlib.RoomEventFilter,
|
filter *gomatrixserverlib.RoomEventFilter,
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ type messagesReq struct {
|
||||||
type messagesResp struct {
|
type messagesResp struct {
|
||||||
Start string `json:"start"`
|
Start string `json:"start"`
|
||||||
StartStream string `json:"start_stream,omitempty"` // NOTSPEC: used by Cerulean, so clients can hit /messages then immediately /sync with a latest sync token
|
StartStream string `json:"start_stream,omitempty"` // NOTSPEC: used by Cerulean, so clients can hit /messages then immediately /sync with a latest sync token
|
||||||
End string `json:"end"`
|
End string `json:"end,omitempty"`
|
||||||
Chunk []gomatrixserverlib.ClientEvent `json:"chunk"`
|
Chunk []gomatrixserverlib.ClientEvent `json:"chunk"`
|
||||||
State []gomatrixserverlib.ClientEvent `json:"state"`
|
State []gomatrixserverlib.ClientEvent `json:"state"`
|
||||||
}
|
}
|
||||||
|
|
@ -200,30 +200,6 @@ func OnIncomingMessagesRequest(
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
// at least fetch the membership events for the users returned in chunk if LazyLoadMembers is set
|
|
||||||
state := []gomatrixserverlib.ClientEvent{}
|
|
||||||
if filter.LazyLoadMembers {
|
|
||||||
membershipToUser := make(map[string]*gomatrixserverlib.HeaderedEvent)
|
|
||||||
for _, evt := range clientEvents {
|
|
||||||
// Don't add membership events the client should already know about
|
|
||||||
if _, cached := lazyLoadCache.IsLazyLoadedUserCached(device, roomID, evt.Sender); cached {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
membership, err := db.GetStateEvent(req.Context(), roomID, gomatrixserverlib.MRoomMember, evt.Sender)
|
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("failed to get membership event for user")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if membership != nil {
|
|
||||||
membershipToUser[evt.Sender] = membership
|
|
||||||
lazyLoadCache.StoreLazyLoadedUser(device, roomID, evt.Sender, membership.EventID())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, evt := range membershipToUser {
|
|
||||||
state = append(state, gomatrixserverlib.HeaderedToClientEvent(evt, gomatrixserverlib.FormatSync))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
util.GetLogger(req.Context()).WithFields(logrus.Fields{
|
util.GetLogger(req.Context()).WithFields(logrus.Fields{
|
||||||
"from": from.String(),
|
"from": from.String(),
|
||||||
"to": to.String(),
|
"to": to.String(),
|
||||||
|
|
@ -237,7 +213,13 @@ func OnIncomingMessagesRequest(
|
||||||
Chunk: clientEvents,
|
Chunk: clientEvents,
|
||||||
Start: start.String(),
|
Start: start.String(),
|
||||||
End: end.String(),
|
End: end.String(),
|
||||||
State: state,
|
}
|
||||||
|
res.applyLazyLoadMembers(req.Context(), db, roomID, device, filter.LazyLoadMembers, lazyLoadCache)
|
||||||
|
|
||||||
|
// If we didn't return any events, set the end to an empty string, so it will be omitted
|
||||||
|
// in the response JSON.
|
||||||
|
if len(res.Chunk) == 0 {
|
||||||
|
res.End = ""
|
||||||
}
|
}
|
||||||
if fromStream != nil {
|
if fromStream != nil {
|
||||||
res.StartStream = fromStream.String()
|
res.StartStream = fromStream.String()
|
||||||
|
|
@ -250,6 +232,40 @@ func OnIncomingMessagesRequest(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// applyLazyLoadMembers loads membership events for users returned in Chunk, if the filter has
|
||||||
|
// LazyLoadMembers enabled.
|
||||||
|
func (m *messagesResp) applyLazyLoadMembers(
|
||||||
|
ctx context.Context,
|
||||||
|
db storage.Database,
|
||||||
|
roomID string,
|
||||||
|
device *userapi.Device,
|
||||||
|
lazyLoad bool,
|
||||||
|
lazyLoadCache caching.LazyLoadCache,
|
||||||
|
) {
|
||||||
|
if !lazyLoad {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
membershipToUser := make(map[string]*gomatrixserverlib.HeaderedEvent)
|
||||||
|
for _, evt := range m.Chunk {
|
||||||
|
// Don't add membership events the client should already know about
|
||||||
|
if _, cached := lazyLoadCache.IsLazyLoadedUserCached(device, roomID, evt.Sender); cached {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
membership, err := db.GetStateEvent(ctx, roomID, gomatrixserverlib.MRoomMember, evt.Sender)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("failed to get membership event for user")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if membership != nil {
|
||||||
|
membershipToUser[evt.Sender] = membership
|
||||||
|
lazyLoadCache.StoreLazyLoadedUser(device, roomID, evt.Sender, membership.EventID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, evt := range membershipToUser {
|
||||||
|
m.State = append(m.State, gomatrixserverlib.HeaderedToClientEvent(evt, gomatrixserverlib.FormatSync))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func checkIsRoomForgotten(ctx context.Context, roomID, userID string, rsAPI api.SyncRoomserverAPI) (forgotten bool, exists bool, err error) {
|
func checkIsRoomForgotten(ctx context.Context, roomID, userID string, rsAPI api.SyncRoomserverAPI) (forgotten bool, exists bool, err error) {
|
||||||
req := api.QueryMembershipForUserRequest{
|
req := api.QueryMembershipForUserRequest{
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ func (p *PresenceStreamProvider) IncrementalSync(
|
||||||
currentlyActive := prevPresence.CurrentlyActive()
|
currentlyActive := prevPresence.CurrentlyActive()
|
||||||
skip := prevPresence.Equals(presence) && currentlyActive && req.Device.UserID != presence.UserID
|
skip := prevPresence.Equals(presence) && currentlyActive && req.Device.UserID != presence.UserID
|
||||||
if skip {
|
if skip {
|
||||||
req.Log.Debugf("Skipping presence, no change (%s)", presence.UserID)
|
req.Log.Tracef("Skipping presence, no change (%s)", presence.UserID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,3 +48,4 @@ Notifications can be viewed with GET /notifications
|
||||||
# More flakey
|
# More flakey
|
||||||
|
|
||||||
If remote user leaves room we no longer receive device updates
|
If remote user leaves room we no longer receive device updates
|
||||||
|
Guest users can join guest_access rooms
|
||||||
|
|
|
||||||
|
|
@ -241,7 +241,6 @@ Inbound federation can receive v2 /send_join
|
||||||
Message history can be paginated
|
Message history can be paginated
|
||||||
Backfill works correctly with history visibility set to joined
|
Backfill works correctly with history visibility set to joined
|
||||||
Guest user cannot call /events globally
|
Guest user cannot call /events globally
|
||||||
Guest users can join guest_access rooms
|
|
||||||
Guest user can set display names
|
Guest user can set display names
|
||||||
Guest user cannot upgrade other users
|
Guest user cannot upgrade other users
|
||||||
Guest non-joined user cannot call /events on shared room
|
Guest non-joined user cannot call /events on shared room
|
||||||
|
|
@ -715,4 +714,9 @@ Presence can be set from sync
|
||||||
PUT /rooms/:room_id/redact/:event_id/:txn_id is idempotent
|
PUT /rooms/:room_id/redact/:event_id/:txn_id is idempotent
|
||||||
Unnamed room comes with a name summary
|
Unnamed room comes with a name summary
|
||||||
Named room comes with just joined member count summary
|
Named room comes with just joined member count summary
|
||||||
Room summary only has 5 heroes
|
Room summary only has 5 heroes
|
||||||
|
registration is idempotent, with username specified
|
||||||
|
Setting state twice is idempotent
|
||||||
|
Joining room twice is idempotent
|
||||||
|
Inbound federation can return missing events for shared visibility
|
||||||
|
Inbound federation ignores redactions from invalid servers room > v3
|
||||||
|
|
@ -33,6 +33,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/internal/pushrules"
|
"github.com/matrix-org/dendrite/internal/pushrules"
|
||||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||||
keyapi "github.com/matrix-org/dendrite/keyserver/api"
|
keyapi "github.com/matrix-org/dendrite/keyserver/api"
|
||||||
|
rsapi "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/dendrite/userapi/producers"
|
"github.com/matrix-org/dendrite/userapi/producers"
|
||||||
|
|
@ -49,6 +50,7 @@ type UserInternalAPI struct {
|
||||||
// AppServices is the list of all registered AS
|
// AppServices is the list of all registered AS
|
||||||
AppServices []config.ApplicationService
|
AppServices []config.ApplicationService
|
||||||
KeyAPI keyapi.UserKeyAPI
|
KeyAPI keyapi.UserKeyAPI
|
||||||
|
RSAPI rsapi.UserRoomserverAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *UserInternalAPI) InputAccountData(ctx context.Context, req *api.InputAccountDataRequest, res *api.InputAccountDataResponse) error {
|
func (a *UserInternalAPI) InputAccountData(ctx context.Context, req *api.InputAccountDataRequest, res *api.InputAccountDataResponse) error {
|
||||||
|
|
@ -452,6 +454,30 @@ func (a *UserInternalAPI) queryAppServiceToken(ctx context.Context, token, appSe
|
||||||
|
|
||||||
// PerformAccountDeactivation deactivates the user's account, removing all ability for the user to login again.
|
// PerformAccountDeactivation deactivates the user's account, removing all ability for the user to login again.
|
||||||
func (a *UserInternalAPI) PerformAccountDeactivation(ctx context.Context, req *api.PerformAccountDeactivationRequest, res *api.PerformAccountDeactivationResponse) error {
|
func (a *UserInternalAPI) PerformAccountDeactivation(ctx context.Context, req *api.PerformAccountDeactivationRequest, res *api.PerformAccountDeactivationResponse) error {
|
||||||
|
evacuateReq := &rsapi.PerformAdminEvacuateUserRequest{
|
||||||
|
UserID: fmt.Sprintf("@%s:%s", req.Localpart, a.ServerName),
|
||||||
|
}
|
||||||
|
evacuateRes := &rsapi.PerformAdminEvacuateUserResponse{}
|
||||||
|
a.RSAPI.PerformAdminEvacuateUser(ctx, evacuateReq, evacuateRes)
|
||||||
|
if err := evacuateRes.Error; err != nil {
|
||||||
|
logrus.WithError(err).Errorf("Failed to evacuate user after account deactivation")
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceReq := &api.PerformDeviceDeletionRequest{
|
||||||
|
UserID: fmt.Sprintf("@%s:%s", req.Localpart, a.ServerName),
|
||||||
|
}
|
||||||
|
deviceRes := &api.PerformDeviceDeletionResponse{}
|
||||||
|
if err := a.PerformDeviceDeletion(ctx, deviceReq, deviceRes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pusherReq := &api.PerformPusherDeletionRequest{
|
||||||
|
Localpart: req.Localpart,
|
||||||
|
}
|
||||||
|
if err := a.PerformPusherDeletion(ctx, pusherReq, &struct{}{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err := a.DB.DeactivateAccount(ctx, req.Localpart)
|
err := a.DB.DeactivateAccount(ctx, req.Localpart)
|
||||||
res.AccountDeactivated = err == nil
|
res.AccountDeactivated = err == nil
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ func NewInternalAPI(
|
||||||
ServerName: cfg.Matrix.ServerName,
|
ServerName: cfg.Matrix.ServerName,
|
||||||
AppServices: appServices,
|
AppServices: appServices,
|
||||||
KeyAPI: keyAPI,
|
KeyAPI: keyAPI,
|
||||||
|
RSAPI: rsAPI,
|
||||||
DisableTLSValidation: cfg.PushGatewayDisableTLSValidation,
|
DisableTLSValidation: cfg.PushGatewayDisableTLSValidation,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue