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/userprofile
This commit is contained in:
commit
4f997608c4
17
CHANGES.md
17
CHANGES.md
|
|
@ -1,5 +1,22 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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)
|
## Dendrite 0.8.7 (2022-06-01)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
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.
|
||||||
|
|
@ -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
|
||||||
|
|
@ -161,3 +163,18 @@ func (p *SyncAPIProducer) SendPresence(
|
||||||
_, 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{}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -512,11 +512,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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -34,7 +34,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-20220607143425-e55d796fd0b3
|
github.com/matrix-org/gomatrixserverlib v0.0.0-20220613132209-aedb3fbb511a
|
||||||
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
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -418,8 +418,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-20220607143425-e55d796fd0b3 h1:2eYcBt8Kg+nW/xIJY5x8Uo2dQLjUF+oxLap00uFC5l8=
|
github.com/matrix-org/gomatrixserverlib v0.0.0-20220613132209-aedb3fbb511a h1:jOkrb6twViAGTHHadA51sQwdloHT0Vx1MCptk9InTHo=
|
||||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20220607143425-e55d796fd0b3/go.mod h1:jX38yp3SSLJNftBg3PXU1ayd0PCLIiDHQ4xAc9DIixk=
|
github.com/matrix-org/gomatrixserverlib v0.0.0-20220613132209-aedb3fbb511a/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=
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ var build string
|
||||||
const (
|
const (
|
||||||
VersionMajor = 0
|
VersionMajor = 0
|
||||||
VersionMinor = 8
|
VersionMinor = 8
|
||||||
VersionPatch = 7
|
VersionPatch = 8
|
||||||
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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -295,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 {
|
||||||
|
|
@ -360,6 +376,7 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
@ -368,7 +385,8 @@ func (r *Inputer) processRoomEvent(
|
||||||
{
|
{
|
||||||
Type: api.OutputTypeOldRoomEvent,
|
Type: api.OutputTypeOldRoomEvent,
|
||||||
OldRoomEvent: &api.OutputOldRoomEvent{
|
OldRoomEvent: &api.OutputOldRoomEvent{
|
||||||
Event: headered,
|
Event: headered,
|
||||||
|
HistoryVisibility: historyVisibility,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -402,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
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ 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")
|
span, ctx := opentracing.StartSpanFromContext(ctx, "updateLatestEvents")
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
|
|
@ -69,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 {
|
||||||
|
|
@ -119,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 {
|
||||||
|
|
@ -365,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()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -45,6 +46,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,
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,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
|
||||||
|
|
|
||||||
|
|
@ -716,6 +716,8 @@ 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
|
Setting state twice is idempotent
|
||||||
Joining room twice is idempotent
|
Joining room twice is idempotent
|
||||||
Inbound federation can return missing events for shared visibility
|
Inbound federation can return missing events for shared visibility
|
||||||
|
Inbound federation ignores redactions from invalid servers room > v3
|
||||||
Loading…
Reference in a new issue