mirror of
https://github.com/matrix-org/dendrite.git
synced 2026-01-01 03:03:10 -06:00
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/systemd
This commit is contained in:
commit
ebdaa5b47f
36
.github/workflows/dendrite.yml
vendored
36
.github/workflows/dendrite.yml
vendored
|
|
@ -73,6 +73,26 @@ jobs:
|
|||
timeout-minutes: 5
|
||||
name: Unit tests (Go ${{ matrix.go }})
|
||||
runs-on: ubuntu-latest
|
||||
# Service containers to run with `container-job`
|
||||
services:
|
||||
# Label used to access the service container
|
||||
postgres:
|
||||
# Docker Hub image
|
||||
image: postgres:13-alpine
|
||||
# Provide the password for postgres
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: dendrite
|
||||
ports:
|
||||
# Maps tcp port 5432 on service container to the host
|
||||
- 5432:5432
|
||||
# Set health checks to wait until postgres has started
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
|
@ -91,7 +111,12 @@ jobs:
|
|||
key: ${{ runner.os }}-go${{ matrix.go }}-test-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go${{ matrix.go }}-test-
|
||||
- run: go test ./...
|
||||
- run: go test -p 1 ./...
|
||||
env:
|
||||
POSTGRES_HOST: localhost
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: dendrite
|
||||
|
||||
# build Dendrite for linux with different architectures and go versions
|
||||
build:
|
||||
|
|
@ -233,7 +258,14 @@ jobs:
|
|||
- name: Summarise results.tap
|
||||
if: ${{ always() }}
|
||||
run: /sytest/scripts/tap_to_gha.pl /logs/results.tap
|
||||
|
||||
- name: Sytest List Maintenance
|
||||
if: ${{ always() }}
|
||||
run: /src/show-expected-fail-tests.sh /logs/results.tap /src/sytest-whitelist /src/sytest-blacklist
|
||||
continue-on-error: true # not fatal
|
||||
- name: Are We Synapse Yet?
|
||||
if: ${{ always() }}
|
||||
run: /src/are-we-synapse-yet.py /logs/results.tap -v
|
||||
continue-on-error: true # not fatal
|
||||
- name: Upload Sytest logs
|
||||
uses: actions/upload-artifact@v2
|
||||
if: ${{ always() }}
|
||||
|
|
|
|||
27
CHANGES.md
27
CHANGES.md
|
|
@ -1,5 +1,32 @@
|
|||
# Changelog
|
||||
|
||||
## Dendrite 0.8.1 (2022-04-07)
|
||||
|
||||
### Fixes
|
||||
|
||||
* A bug which could result in the sync API deadlocking due to lock contention in the notifier has been fixed
|
||||
|
||||
## Dendrite 0.8.0 (2022-04-07)
|
||||
|
||||
### Features
|
||||
|
||||
* Support for presence has been added
|
||||
* Presence is not enabled by default
|
||||
* The `global.presence.enable_inbound` and `global.presence.enable_outbound` configuration options allow configuring inbound and outbound presence separately
|
||||
* Support for room upgrades via the `/room/{roomID}/upgrade` endpoint has been added (contributed by [DavidSpenler](https://github.com/DavidSpenler), [alexkursell](https://github.com/alexkursell))
|
||||
* Support for ignoring users has been added
|
||||
* Joined and invite user counts are now sent in the `/sync` room summaries
|
||||
* Queued federation and stale device list updates will now be staggered at startup over an up-to 2 minute warm-up period, rather than happening all at once
|
||||
* Memory pressure created by the sync notifier has been reduced
|
||||
* The EDU server component has now been removed, with the work being moved to more relevant components
|
||||
|
||||
### Fixes
|
||||
|
||||
* It is now possible to set the `power_level_content_override` when creating a room to include power levels over 100
|
||||
* `/send_join` and `/state` responses will now not unmarshal the JSON twice
|
||||
* The stream event consumer for push notifications will no longer request membership events that are irrelevant
|
||||
* Appservices will no longer incorrectly receive state events twice
|
||||
|
||||
## Dendrite 0.7.0 (2022-03-25)
|
||||
|
||||
### Features
|
||||
|
|
|
|||
13
README.md
13
README.md
|
|
@ -82,20 +82,17 @@ Then point your favourite Matrix client at `http://localhost:8008` or `https://l
|
|||
|
||||
We use a script called Are We Synapse Yet which checks Sytest compliance rates. Sytest is a black-box homeserver
|
||||
test rig with around 900 tests. The script works out how many of these tests are passing on Dendrite and it
|
||||
updates with CI. As of March 2022 we're at around 76% CS API coverage and 95% Federation coverage, though check
|
||||
updates with CI. As of April 2022 we're at around 83% CS API coverage and 95% Federation coverage, though check
|
||||
CI for the latest numbers. In practice, this means you can communicate locally and via federation with Synapse
|
||||
servers such as matrix.org reasonably well. There's a long list of features that are not implemented, notably:
|
||||
|
||||
- Search
|
||||
- User Directory
|
||||
- Presence
|
||||
servers such as matrix.org reasonably well, although there are still some missing features (like Search).
|
||||
|
||||
We are prioritising features that will benefit single-user homeservers first (e.g Receipts, E2E) rather
|
||||
than features that massive deployments may be interested in (User Directory, OpenID, Guests, Admin APIs, AS API).
|
||||
This means Dendrite supports amongst others:
|
||||
|
||||
- Core room functionality (creating rooms, invites, auth rules)
|
||||
- Federation in rooms v1-v7
|
||||
- Full support for room versions 1 to 7
|
||||
- Experimental support for room versions 8 to 9
|
||||
- Backfilling locally and via federation
|
||||
- Accounts, Profiles and Devices
|
||||
- Published room lists
|
||||
|
|
@ -108,6 +105,8 @@ This means Dendrite supports amongst others:
|
|||
- Receipts
|
||||
- Push
|
||||
- Guests
|
||||
- User Directory
|
||||
- Presence
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
|||
|
|
@ -83,20 +83,29 @@ func (s *OutputRoomEventConsumer) onMessage(ctx context.Context, msg *nats.Msg)
|
|||
return true
|
||||
}
|
||||
|
||||
if output.Type != api.OutputTypeNewRoomEvent {
|
||||
if output.Type != api.OutputTypeNewRoomEvent || output.NewRoomEvent == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
events := []*gomatrixserverlib.HeaderedEvent{output.NewRoomEvent.Event}
|
||||
newEventID := output.NewRoomEvent.Event.EventID()
|
||||
events := make([]*gomatrixserverlib.HeaderedEvent, 0, len(output.NewRoomEvent.AddsStateEventIDs))
|
||||
events = append(events, output.NewRoomEvent.Event)
|
||||
if len(output.NewRoomEvent.AddsStateEventIDs) > 0 {
|
||||
eventsReq := &api.QueryEventsByIDRequest{
|
||||
EventIDs: output.NewRoomEvent.AddsStateEventIDs,
|
||||
EventIDs: make([]string, 0, len(output.NewRoomEvent.AddsStateEventIDs)),
|
||||
}
|
||||
eventsRes := &api.QueryEventsByIDResponse{}
|
||||
if err := s.rsAPI.QueryEventsByID(s.ctx, eventsReq, eventsRes); err != nil {
|
||||
return false
|
||||
for _, eventID := range output.NewRoomEvent.AddsStateEventIDs {
|
||||
if eventID != newEventID {
|
||||
eventsReq.EventIDs = append(eventsReq.EventIDs, eventID)
|
||||
}
|
||||
}
|
||||
if len(eventsReq.EventIDs) > 0 {
|
||||
if err := s.rsAPI.QueryEventsByID(s.ctx, eventsReq, eventsRes); err != nil {
|
||||
return false
|
||||
}
|
||||
events = append(events, eventsRes.Events...)
|
||||
}
|
||||
events = append(events, eventsRes.Events...)
|
||||
}
|
||||
|
||||
// Send event to any relevant application services
|
||||
|
|
|
|||
|
|
@ -106,10 +106,13 @@ rst Users cannot set notifications powerlevel higher than their own (2 subtests)
|
|||
rst Both GET and PUT work
|
||||
rct POST /rooms/:room_id/receipt can create receipts
|
||||
red POST /rooms/:room_id/read_markers can create read marker
|
||||
med POST /media/v3/upload can create an upload
|
||||
med POST /media/r0/upload can create an upload
|
||||
med GET /media/v3/download can fetch the value again
|
||||
med GET /media/r0/download can fetch the value again
|
||||
cap GET /capabilities is present and well formed for registered user
|
||||
cap GET /r0/capabilities is not public
|
||||
cap GET /v3/capabilities is not public
|
||||
reg Register with a recaptcha
|
||||
reg registration is idempotent, without username specified
|
||||
reg registration is idempotent, with username specified
|
||||
|
|
@ -174,7 +177,9 @@ eph Ephemeral messages received from clients are correctly expired
|
|||
ali Room aliases can contain Unicode
|
||||
f,ali Remote room alias queries can handle Unicode
|
||||
ali Canonical alias can be set
|
||||
ali Canonical alias can be set (3 subtests)
|
||||
ali Canonical alias can include alt_aliases
|
||||
ali Canonical alias can include alt_aliases (4 subtests)
|
||||
ali Regular users can add and delete aliases in the default room configuration
|
||||
ali Regular users can add and delete aliases when m.room.aliases is restricted
|
||||
ali Deleting a non-existent alias should return a 404
|
||||
|
|
@ -478,6 +483,30 @@ rmv Inbound federation rejects invites which include invalid JSON for room versi
|
|||
rmv Outbound federation rejects invite response which include invalid JSON for room version 6
|
||||
rmv Inbound federation rejects invite rejections which include invalid JSON for room version 6
|
||||
rmv Server rejects invalid JSON in a version 6 room
|
||||
rmv User can create and send/receive messages in a room with version 7 (2 subtests)
|
||||
rmv local user can join room with version 7
|
||||
rmv User can invite local user to room with version 7
|
||||
rmv remote user can join room with version 7
|
||||
rmv User can invite remote user to room with version 7
|
||||
rmv Remote user can backfill in a room with version 7
|
||||
rmv Can reject invites over federation for rooms with version 7
|
||||
rmv Can receive redactions from regular users over federation in room version 7
|
||||
rmv User can create and send/receive messages in a room with version 8 (2 subtests)
|
||||
rmv local user can join room with version 8
|
||||
rmv User can invite local user to room with version 8
|
||||
rmv remote user can join room with version 8
|
||||
rmv User can invite remote user to room with version 8
|
||||
rmv Remote user can backfill in a room with version 8
|
||||
rmv Can reject invites over federation for rooms with version 8
|
||||
rmv Can receive redactions from regular users over federation in room version 8
|
||||
rmv User can create and send/receive messages in a room with version 9 (2 subtests)
|
||||
rmv local user can join room with version 9
|
||||
rmv User can invite local user to room with version 9
|
||||
rmv remote user can join room with version 9
|
||||
rmv User can invite remote user to room with version 9
|
||||
rmv Remote user can backfill in a room with version 9
|
||||
rmv Can reject invites over federation for rooms with version 9
|
||||
rmv Can receive redactions from regular users over federation in room version 9
|
||||
pre Presence changes are reported to local room members
|
||||
f,pre Presence changes are also reported to remote room members
|
||||
pre Presence changes to UNAVAILABLE are reported to local room members
|
||||
|
|
@ -772,12 +801,15 @@ app AS can make room aliases
|
|||
app Regular users cannot create room aliases within the AS namespace
|
||||
app AS-ghosted users can use rooms via AS
|
||||
app AS-ghosted users can use rooms themselves
|
||||
app AS-ghosted users can use rooms via AS (2 subtests)
|
||||
app AS-ghosted users can use rooms themselves (3 subtests)
|
||||
app Ghost user must register before joining room
|
||||
app AS can set avatar for ghosted users
|
||||
app AS can set displayname for ghosted users
|
||||
app AS can't set displayname for random users
|
||||
app Inviting an AS-hosted user asks the AS server
|
||||
app Accesing an AS-hosted room alias asks the AS server
|
||||
app Accesing an AS-hosted room alias asks the AS server (2 subtests)
|
||||
app Events in rooms with AS-hosted room aliases are sent to AS server
|
||||
app AS user (not ghost) can join room without registering
|
||||
app AS user (not ghost) can join room without registering, with user_id query param
|
||||
|
|
@ -789,6 +821,8 @@ app AS can publish rooms in their own list
|
|||
app AS and main public room lists are separate
|
||||
app AS can deactivate a user
|
||||
psh Test that a message is pushed
|
||||
psh Test that a message is pushed (6 subtests)
|
||||
psh Test that rejected pushers are removed. (4 subtests)
|
||||
psh Invites are pushed
|
||||
psh Rooms with names are correctly named in pushed
|
||||
psh Rooms with canonical alias are correctly named in pushed
|
||||
|
|
@ -857,9 +891,12 @@ pre Left room members do not cause problems for presence
|
|||
crm Rooms can be created with an initial invite list (SYN-205) (1 subtests)
|
||||
typ Typing notifications don't leak
|
||||
ban Non-present room members cannot ban others
|
||||
ban Non-present room members cannot ban others (3 subtests)
|
||||
psh Getting push rules doesn't corrupt the cache SYN-390
|
||||
psh Getting push rules doesn't corrupt the cache SYN-390 (3 subtests)
|
||||
inv Test that we can be reinvited to a room we created
|
||||
syn Multiple calls to /sync should not cause 500 errors
|
||||
syn Multiple calls to /sync should not cause 500 errors (6 subtests)
|
||||
gst Guest user can call /events on another world_readable room (SYN-606)
|
||||
gst Real user can call /events on another world_readable room (SYN-606)
|
||||
gst Events come down the correct room
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
from __future__ import division
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Usage: $ ./are-we-synapse-yet.py [-v] results.tap
|
||||
# This script scans a results.tap file from Dendrite's CI process and spits out
|
||||
|
|
@ -156,6 +156,7 @@ def parse_test_line(line):
|
|||
# ✓ POST /register downcases capitals in usernames
|
||||
# ...
|
||||
def print_stats(header_name, gid_to_tests, gid_to_name, verbose):
|
||||
ci = os.getenv("CI") # When running from GHA, this groups the subsections
|
||||
subsections = [] # Registration: 100% (13/13 tests)
|
||||
subsection_test_names = {} # 'subsection name': ["✓ Test 1", "✓ Test 2", "× Test 3"]
|
||||
total_passing = 0
|
||||
|
|
@ -169,7 +170,7 @@ def print_stats(header_name, gid_to_tests, gid_to_name, verbose):
|
|||
for name, passing in tests.items():
|
||||
if passing:
|
||||
group_passing += 1
|
||||
test_names_and_marks.append(f"{'✓' if passing else '×'} {name}")
|
||||
test_names_and_marks.append(f"{'✅' if passing else '❌'} {name}")
|
||||
|
||||
total_tests += group_total
|
||||
total_passing += group_passing
|
||||
|
|
@ -186,11 +187,11 @@ def print_stats(header_name, gid_to_tests, gid_to_name, verbose):
|
|||
print("%s: %s (%d/%d tests)" % (header_name, pct, total_passing, total_tests))
|
||||
print("-" * (len(header_name)+1))
|
||||
for line in subsections:
|
||||
print(" %s" % (line,))
|
||||
print("%s%s" % ("::group::" if ci and verbose else "", line,))
|
||||
if verbose:
|
||||
for test_name_and_pass_mark in subsection_test_names[line]:
|
||||
print(" %s" % (test_name_and_pass_mark,))
|
||||
print("")
|
||||
print("%s" % ("::endgroup::" if ci else ""))
|
||||
print("")
|
||||
|
||||
def main(results_tap_path, verbose):
|
||||
|
|
|
|||
51
build.cmd
Normal file
51
build.cmd
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
@echo off
|
||||
|
||||
:ENTRY_POINT
|
||||
setlocal EnableDelayedExpansion
|
||||
|
||||
REM script base dir
|
||||
set SCRIPTDIR=%~dp0
|
||||
set PROJDIR=%SCRIPTDIR:~0,-1%
|
||||
|
||||
REM Put installed packages into ./bin
|
||||
set GOBIN=%PROJDIR%\bin
|
||||
|
||||
set FLAGS=
|
||||
|
||||
REM Check if sources are under Git control
|
||||
if not exist ".git" goto :CHECK_BIN
|
||||
|
||||
REM set BUILD=`git rev-parse --short HEAD \\ ""`
|
||||
FOR /F "tokens=*" %%X IN ('git rev-parse --short HEAD') DO (
|
||||
set BUILD=%%X
|
||||
)
|
||||
|
||||
REM set BRANCH=`(git symbolic-ref --short HEAD \ tr -d \/ ) \\ ""`
|
||||
FOR /F "tokens=*" %%X IN ('git symbolic-ref --short HEAD') DO (
|
||||
set BRANCHRAW=%%X
|
||||
set BRANCH=!BRANCHRAW:/=!
|
||||
)
|
||||
if "%BRANCH%" == "main" set BRANCH=
|
||||
|
||||
set FLAGS=-X github.com/matrix-org/dendrite/internal.branch=%BRANCH% -X github.com/matrix-org/dendrite/internal.build=%BUILD%
|
||||
|
||||
:CHECK_BIN
|
||||
if exist "bin" goto :ALL_SET
|
||||
mkdir "bin"
|
||||
|
||||
:ALL_SET
|
||||
set CGO_ENABLED=1
|
||||
for /D %%P in (cmd\*) do (
|
||||
go build -trimpath -ldflags "%FLAGS%" -v -o ".\bin" ".\%%P"
|
||||
)
|
||||
|
||||
set CGO_ENABLED=0
|
||||
set GOOS=js
|
||||
set GOARCH=wasm
|
||||
go build -trimpath -ldflags "%FLAGS%" -o bin\main.wasm .\cmd\dendritejs-pinecone
|
||||
|
||||
goto :DONE
|
||||
|
||||
:DONE
|
||||
echo Done
|
||||
endlocal
|
||||
|
|
@ -62,6 +62,17 @@ global:
|
|||
- matrix.org
|
||||
- vector.im
|
||||
|
||||
# Disables federation. Dendrite will not be able to make any outbound HTTP requests
|
||||
# to other servers and the federation API will not be exposed.
|
||||
disable_federation: false
|
||||
|
||||
# Configures the handling of presence events.
|
||||
presence:
|
||||
# Whether inbound presence events are allowed, e.g. receiving presence events from other servers
|
||||
enable_inbound: false
|
||||
# Whether outbound presence events are allowed, e.g. sending presence events to other servers
|
||||
enable_outbound: false
|
||||
|
||||
# Configuration for NATS JetStream
|
||||
jetstream:
|
||||
# A list of NATS Server addresses to connect to. If none are specified, an
|
||||
|
|
@ -160,12 +171,6 @@ client_api:
|
|||
threshold: 5
|
||||
cooloff_ms: 500
|
||||
|
||||
# Configuration for the EDU server.
|
||||
edu_server:
|
||||
internal_api:
|
||||
listen: http://0.0.0.0:7778
|
||||
connect: http://edu_server:7778
|
||||
|
||||
# Configuration for the Federation API.
|
||||
federation_api:
|
||||
internal_api:
|
||||
|
|
@ -179,12 +184,6 @@ federation_api:
|
|||
max_idle_conns: 2
|
||||
conn_max_lifetime: -1
|
||||
|
||||
# List of paths to X.509 certificates to be used by the external federation listeners.
|
||||
# These certificates will be used to calculate the TLS fingerprints and other servers
|
||||
# will expect the certificate to match these fingerprints. Certificates must be in PEM
|
||||
# format.
|
||||
federation_certificates: []
|
||||
|
||||
# How many times we will try to resend a failed transaction to a specific server. The
|
||||
# backoff is 2**x seconds, so 1 = 2 seconds, 2 = 4 seconds, 3 = 8 seconds etc.
|
||||
send_max_retries: 16
|
||||
|
|
|
|||
|
|
@ -84,18 +84,6 @@ services:
|
|||
- internal
|
||||
restart: unless-stopped
|
||||
|
||||
edu_server:
|
||||
hostname: edu_server
|
||||
image: matrixdotorg/dendrite-polylith:latest
|
||||
command: eduserver
|
||||
volumes:
|
||||
- ./config:/etc/dendrite
|
||||
depends_on:
|
||||
- jetstream
|
||||
networks:
|
||||
- internal
|
||||
restart: unless-stopped
|
||||
|
||||
federation_api:
|
||||
hostname: federation_api
|
||||
image: matrixdotorg/dendrite-polylith:latest
|
||||
|
|
|
|||
|
|
@ -1,3 +1,17 @@
|
|||
// 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 gobind
|
||||
|
||||
import (
|
||||
|
|
@ -9,7 +23,6 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
|
@ -22,11 +35,9 @@ import (
|
|||
"github.com/matrix-org/dendrite/clientapi/userutil"
|
||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/conn"
|
||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/rooms"
|
||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/users"
|
||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing"
|
||||
"github.com/matrix-org/dendrite/eduserver"
|
||||
"github.com/matrix-org/dendrite/eduserver/cache"
|
||||
"github.com/matrix-org/dendrite/federationapi"
|
||||
"github.com/matrix-org/dendrite/federationapi/api"
|
||||
"github.com/matrix-org/dendrite/internal/httputil"
|
||||
"github.com/matrix-org/dendrite/keyserver"
|
||||
"github.com/matrix-org/dendrite/roomserver"
|
||||
|
|
@ -41,6 +52,7 @@ import (
|
|||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
|
||||
pineconeConnections "github.com/matrix-org/pinecone/connections"
|
||||
pineconeMulticast "github.com/matrix-org/pinecone/multicast"
|
||||
pineconeRouter "github.com/matrix-org/pinecone/router"
|
||||
pineconeSessions "github.com/matrix-org/pinecone/sessions"
|
||||
|
|
@ -60,11 +72,9 @@ type DendriteMonolith struct {
|
|||
PineconeRouter *pineconeRouter.Router
|
||||
PineconeMulticast *pineconeMulticast.Multicast
|
||||
PineconeQUIC *pineconeSessions.Sessions
|
||||
PineconeManager *pineconeConnections.ConnectionManager
|
||||
StorageDirectory string
|
||||
CacheDirectory string
|
||||
staticPeerURI string
|
||||
staticPeerMutex sync.RWMutex
|
||||
staticPeerAttempt chan struct{}
|
||||
listener net.Listener
|
||||
httpServer *http.Server
|
||||
processContext *process.ProcessContext
|
||||
|
|
@ -80,7 +90,7 @@ func (m *DendriteMonolith) PeerCount(peertype int) int {
|
|||
}
|
||||
|
||||
func (m *DendriteMonolith) SessionCount() int {
|
||||
return len(m.PineconeQUIC.Sessions())
|
||||
return len(m.PineconeQUIC.Protocol("matrix").Sessions())
|
||||
}
|
||||
|
||||
func (m *DendriteMonolith) SetMulticastEnabled(enabled bool) {
|
||||
|
|
@ -93,15 +103,8 @@ func (m *DendriteMonolith) SetMulticastEnabled(enabled bool) {
|
|||
}
|
||||
|
||||
func (m *DendriteMonolith) SetStaticPeer(uri string) {
|
||||
m.staticPeerMutex.Lock()
|
||||
m.staticPeerURI = strings.TrimSpace(uri)
|
||||
m.staticPeerMutex.Unlock()
|
||||
m.DisconnectType(int(pineconeRouter.PeerTypeRemote))
|
||||
if uri != "" {
|
||||
go func() {
|
||||
m.staticPeerAttempt <- struct{}{}
|
||||
}()
|
||||
}
|
||||
m.PineconeManager.RemovePeers()
|
||||
m.PineconeManager.AddPeer(strings.TrimSpace(uri))
|
||||
}
|
||||
|
||||
func (m *DendriteMonolith) DisconnectType(peertype int) {
|
||||
|
|
@ -199,43 +202,6 @@ func (m *DendriteMonolith) RegisterDevice(localpart, deviceID string) (string, e
|
|||
return loginRes.Device.AccessToken, nil
|
||||
}
|
||||
|
||||
func (m *DendriteMonolith) staticPeerConnect() {
|
||||
connected := map[string]bool{} // URI -> connected?
|
||||
attempt := func() {
|
||||
m.staticPeerMutex.RLock()
|
||||
uri := m.staticPeerURI
|
||||
m.staticPeerMutex.RUnlock()
|
||||
if uri == "" {
|
||||
return
|
||||
}
|
||||
for k := range connected {
|
||||
delete(connected, k)
|
||||
}
|
||||
for _, uri := range strings.Split(uri, ",") {
|
||||
connected[strings.TrimSpace(uri)] = false
|
||||
}
|
||||
for _, info := range m.PineconeRouter.Peers() {
|
||||
connected[info.URI] = true
|
||||
}
|
||||
for k, online := range connected {
|
||||
if !online {
|
||||
if err := conn.ConnectToPeer(m.PineconeRouter, k); err != nil {
|
||||
logrus.WithError(err).Error("Failed to connect to static peer")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-m.processContext.Context().Done():
|
||||
case <-m.staticPeerAttempt:
|
||||
attempt()
|
||||
case <-time.After(time.Second * 5):
|
||||
attempt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nolint:gocyclo
|
||||
func (m *DendriteMonolith) Start() {
|
||||
var err error
|
||||
|
|
@ -259,7 +225,7 @@ func (m *DendriteMonolith) Start() {
|
|||
pk = sk.Public().(ed25519.PublicKey)
|
||||
}
|
||||
|
||||
m.listener, err = net.Listen("tcp", "localhost:65432")
|
||||
m.listener, err = net.Listen("tcp", ":65432")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
@ -270,10 +236,10 @@ func (m *DendriteMonolith) Start() {
|
|||
m.logger.SetOutput(BindLogger{})
|
||||
logrus.SetOutput(BindLogger{})
|
||||
|
||||
logger := log.New(os.Stdout, "PINECONE: ", 0)
|
||||
m.PineconeRouter = pineconeRouter.NewRouter(logger, sk, false)
|
||||
m.PineconeQUIC = pineconeSessions.NewSessions(logger, m.PineconeRouter)
|
||||
m.PineconeMulticast = pineconeMulticast.NewMulticast(logger, m.PineconeRouter)
|
||||
m.PineconeRouter = pineconeRouter.NewRouter(logrus.WithField("pinecone", "router"), sk, false)
|
||||
m.PineconeQUIC = pineconeSessions.NewSessions(logrus.WithField("pinecone", "sessions"), m.PineconeRouter, []string{"matrix"})
|
||||
m.PineconeMulticast = pineconeMulticast.NewMulticast(logrus.WithField("pinecone", "multicast"), m.PineconeRouter)
|
||||
m.PineconeManager = pineconeConnections.NewConnectionManager(m.PineconeRouter)
|
||||
|
||||
prefix := hex.EncodeToString(pk)
|
||||
cfg := &config.Dendrite{}
|
||||
|
|
@ -281,6 +247,7 @@ func (m *DendriteMonolith) Start() {
|
|||
cfg.Global.ServerName = gomatrixserverlib.ServerName(hex.EncodeToString(pk))
|
||||
cfg.Global.PrivateKey = sk
|
||||
cfg.Global.KeyID = gomatrixserverlib.KeyID(signing.KeyID)
|
||||
cfg.Global.JetStream.InMemory = true
|
||||
cfg.Global.JetStream.StoragePath = config.Path(fmt.Sprintf("%s/%s", m.StorageDirectory, prefix))
|
||||
cfg.UserAPI.AccountDatabase.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/%s-account.db", m.StorageDirectory, prefix))
|
||||
cfg.MediaAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/dendrite-p2p-mediaapi.db", m.StorageDirectory))
|
||||
|
|
@ -315,16 +282,15 @@ func (m *DendriteMonolith) Start() {
|
|||
m.userAPI = userapi.NewInternalAPI(base, accountDB, &cfg.UserAPI, cfg.Derived.ApplicationServices, keyAPI, rsAPI, base.PushGatewayHTTPClient())
|
||||
keyAPI.SetUserAPI(m.userAPI)
|
||||
|
||||
eduInputAPI := eduserver.NewInternalAPI(
|
||||
base, cache.New(), m.userAPI,
|
||||
)
|
||||
|
||||
asAPI := appservice.NewInternalAPI(base, m.userAPI, rsAPI)
|
||||
|
||||
// The underlying roomserver implementation needs to be able to call the fedsender.
|
||||
// This is different to rsAPI which can be the http client which doesn't need this dependency
|
||||
rsAPI.SetFederationAPI(fsAPI, keyRing)
|
||||
|
||||
userProvider := users.NewPineconeUserProvider(m.PineconeRouter, m.PineconeQUIC, m.userAPI, federation)
|
||||
roomProvider := rooms.NewPineconeRoomProvider(m.PineconeRouter, m.PineconeQUIC, fsAPI, federation)
|
||||
|
||||
monolith := setup.Monolith{
|
||||
Config: base.Cfg,
|
||||
AccountDB: accountDB,
|
||||
|
|
@ -332,13 +298,13 @@ func (m *DendriteMonolith) Start() {
|
|||
FedClient: federation,
|
||||
KeyRing: keyRing,
|
||||
|
||||
AppserviceAPI: asAPI,
|
||||
EDUInternalAPI: eduInputAPI,
|
||||
FederationAPI: fsAPI,
|
||||
RoomserverAPI: rsAPI,
|
||||
UserAPI: m.userAPI,
|
||||
KeyAPI: keyAPI,
|
||||
ExtPublicRoomsProvider: rooms.NewPineconeRoomProvider(m.PineconeRouter, m.PineconeQUIC, fsAPI, federation),
|
||||
AppserviceAPI: asAPI,
|
||||
FederationAPI: fsAPI,
|
||||
RoomserverAPI: rsAPI,
|
||||
UserAPI: m.userAPI,
|
||||
KeyAPI: keyAPI,
|
||||
ExtPublicRoomsProvider: roomProvider,
|
||||
ExtUserDirectoryProvider: userProvider,
|
||||
}
|
||||
monolith.AddAllPublicRoutes(
|
||||
base.ProcessContext,
|
||||
|
|
@ -354,12 +320,15 @@ func (m *DendriteMonolith) Start() {
|
|||
httpRouter.PathPrefix(httputil.InternalPathPrefix).Handler(base.InternalAPIMux)
|
||||
httpRouter.PathPrefix(httputil.PublicClientPathPrefix).Handler(base.PublicClientAPIMux)
|
||||
httpRouter.PathPrefix(httputil.PublicMediaPathPrefix).Handler(base.PublicMediaAPIMux)
|
||||
httpRouter.HandleFunc("/pinecone", m.PineconeRouter.ManholeHandler)
|
||||
|
||||
pMux := mux.NewRouter().SkipClean(true).UseEncodedPath()
|
||||
pMux.PathPrefix(users.PublicURL).HandlerFunc(userProvider.FederatedUserProfiles)
|
||||
pMux.PathPrefix(httputil.PublicFederationPathPrefix).Handler(base.PublicFederationAPIMux)
|
||||
pMux.PathPrefix(httputil.PublicMediaPathPrefix).Handler(base.PublicMediaAPIMux)
|
||||
|
||||
pHTTP := m.PineconeQUIC.HTTP()
|
||||
pHTTP := m.PineconeQUIC.Protocol("matrix").HTTP()
|
||||
pHTTP.Mux().Handle(users.PublicURL, pMux)
|
||||
pHTTP.Mux().Handle(httputil.PublicFederationPathPrefix, pMux)
|
||||
pHTTP.Mux().Handle(httputil.PublicMediaPathPrefix, pMux)
|
||||
|
||||
|
|
@ -379,33 +348,23 @@ func (m *DendriteMonolith) Start() {
|
|||
|
||||
m.processContext = base.ProcessContext
|
||||
|
||||
m.staticPeerAttempt = make(chan struct{}, 1)
|
||||
go m.staticPeerConnect()
|
||||
|
||||
go func() {
|
||||
m.logger.Info("Listening on ", cfg.Global.ServerName)
|
||||
m.logger.Fatal(m.httpServer.Serve(m.PineconeQUIC))
|
||||
m.logger.Fatal(m.httpServer.Serve(m.PineconeQUIC.Protocol("matrix")))
|
||||
}()
|
||||
go func() {
|
||||
logrus.Info("Listening on ", m.listener.Addr())
|
||||
logrus.Fatal(http.Serve(m.listener, httpRouter))
|
||||
}()
|
||||
go func() {
|
||||
logrus.Info("Sending wake-up message to known nodes")
|
||||
req := &api.PerformBroadcastEDURequest{}
|
||||
res := &api.PerformBroadcastEDUResponse{}
|
||||
if err := fsAPI.PerformBroadcastEDU(context.TODO(), req, res); err != nil {
|
||||
logrus.WithError(err).Error("Failed to send wake-up message to known nodes")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *DendriteMonolith) Stop() {
|
||||
m.processContext.ShutdownDendrite()
|
||||
_ = m.listener.Close()
|
||||
m.PineconeMulticast.Stop()
|
||||
_ = m.PineconeQUIC.Close()
|
||||
m.processContext.ShutdownDendrite()
|
||||
_ = m.PineconeRouter.Close()
|
||||
m.processContext.WaitForComponentsToFinish()
|
||||
}
|
||||
|
||||
const MaxFrameSize = types.MaxFrameSize
|
||||
|
|
|
|||
|
|
@ -1,3 +1,17 @@
|
|||
// 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.
|
||||
|
||||
//go:build ios
|
||||
// +build ios
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,17 @@
|
|||
// 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.
|
||||
|
||||
//go:build !ios
|
||||
// +build !ios
|
||||
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@ import (
|
|||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing"
|
||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/yggconn"
|
||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/yggrooms"
|
||||
"github.com/matrix-org/dendrite/eduserver"
|
||||
"github.com/matrix-org/dendrite/eduserver/cache"
|
||||
"github.com/matrix-org/dendrite/federationapi"
|
||||
"github.com/matrix-org/dendrite/federationapi/api"
|
||||
"github.com/matrix-org/dendrite/internal/httputil"
|
||||
|
|
@ -23,6 +21,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/setup"
|
||||
"github.com/matrix-org/dendrite/setup/base"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
"github.com/matrix-org/dendrite/userapi"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
|
@ -36,6 +35,7 @@ type DendriteMonolith struct {
|
|||
StorageDirectory string
|
||||
listener net.Listener
|
||||
httpServer *http.Server
|
||||
processContext *process.ProcessContext
|
||||
}
|
||||
|
||||
func (m *DendriteMonolith) BaseURL() string {
|
||||
|
|
@ -87,6 +87,7 @@ func (m *DendriteMonolith) Start() {
|
|||
cfg.Global.PrivateKey = ygg.PrivateKey()
|
||||
cfg.Global.KeyID = gomatrixserverlib.KeyID(signing.KeyID)
|
||||
cfg.Global.JetStream.StoragePath = config.Path(fmt.Sprintf("%s/", m.StorageDirectory))
|
||||
cfg.Global.JetStream.InMemory = true
|
||||
cfg.UserAPI.AccountDatabase.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/dendrite-p2p-account.db", m.StorageDirectory))
|
||||
cfg.MediaAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/dendrite-p2p-mediaapi.db", m.StorageDirectory))
|
||||
cfg.SyncAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/dendrite-p2p-syncapi.db", m.StorageDirectory))
|
||||
|
|
@ -101,6 +102,7 @@ func (m *DendriteMonolith) Start() {
|
|||
}
|
||||
|
||||
base := base.NewBaseDendrite(cfg, "Monolith")
|
||||
m.processContext = base.ProcessContext
|
||||
defer base.Close() // nolint: errcheck
|
||||
|
||||
accountDB := base.CreateAccountsDB()
|
||||
|
|
@ -119,10 +121,6 @@ func (m *DendriteMonolith) Start() {
|
|||
userAPI := userapi.NewInternalAPI(base, accountDB, &cfg.UserAPI, cfg.Derived.ApplicationServices, keyAPI, rsAPI, base.PushGatewayHTTPClient())
|
||||
keyAPI.SetUserAPI(userAPI)
|
||||
|
||||
eduInputAPI := eduserver.NewInternalAPI(
|
||||
base, cache.New(), userAPI,
|
||||
)
|
||||
|
||||
asAPI := appservice.NewInternalAPI(base, userAPI, rsAPI)
|
||||
rsAPI.SetAppserviceAPI(asAPI)
|
||||
|
||||
|
|
@ -137,12 +135,11 @@ func (m *DendriteMonolith) Start() {
|
|||
FedClient: federation,
|
||||
KeyRing: keyRing,
|
||||
|
||||
AppserviceAPI: asAPI,
|
||||
EDUInternalAPI: eduInputAPI,
|
||||
FederationAPI: fsAPI,
|
||||
RoomserverAPI: rsAPI,
|
||||
UserAPI: userAPI,
|
||||
KeyAPI: keyAPI,
|
||||
AppserviceAPI: asAPI,
|
||||
FederationAPI: fsAPI,
|
||||
RoomserverAPI: rsAPI,
|
||||
UserAPI: userAPI,
|
||||
KeyAPI: keyAPI,
|
||||
ExtPublicRoomsProvider: yggrooms.NewYggdrasilRoomProvider(
|
||||
ygg, fsAPI, federation,
|
||||
),
|
||||
|
|
@ -197,9 +194,12 @@ func (m *DendriteMonolith) Start() {
|
|||
}()
|
||||
}
|
||||
|
||||
func (m *DendriteMonolith) Suspend() {
|
||||
m.logger.Info("Suspending monolith")
|
||||
func (m *DendriteMonolith) Stop() {
|
||||
if err := m.httpServer.Close(); err != nil {
|
||||
m.logger.Warn("Error stopping HTTP server:", err)
|
||||
}
|
||||
if m.processContext != nil {
|
||||
m.processContext.ShutdownDendrite()
|
||||
m.processContext.WaitForComponentsToFinish()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ package authtypes
|
|||
// Profile represents the profile for a Matrix account.
|
||||
type Profile struct {
|
||||
Localpart string `json:"local_part"`
|
||||
ServerName string `json:"server_name,omitempty"` // NOTSPEC: only set by Pinecone user provider
|
||||
DisplayName string `json:"display_name"`
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import (
|
|||
"github.com/matrix-org/dendrite/clientapi/api"
|
||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||
"github.com/matrix-org/dendrite/clientapi/routing"
|
||||
eduServerAPI "github.com/matrix-org/dendrite/eduserver/api"
|
||||
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
||||
"github.com/matrix-org/dendrite/internal/transactions"
|
||||
keyserverAPI "github.com/matrix-org/dendrite/keyserver/api"
|
||||
|
|
@ -40,26 +39,32 @@ func AddPublicRoutes(
|
|||
cfg *config.ClientAPI,
|
||||
federation *gomatrixserverlib.FederationClient,
|
||||
rsAPI roomserverAPI.RoomserverInternalAPI,
|
||||
eduInputAPI eduServerAPI.EDUServerInputAPI,
|
||||
asAPI appserviceAPI.AppServiceQueryAPI,
|
||||
transactionsCache *transactions.Cache,
|
||||
fsAPI federationAPI.FederationInternalAPI,
|
||||
userAPI userapi.UserInternalAPI,
|
||||
userDirectoryProvider userapi.UserDirectoryProvider,
|
||||
keyAPI keyserverAPI.KeyInternalAPI,
|
||||
extRoomsProvider api.ExtraPublicRoomsProvider,
|
||||
mscCfg *config.MSCs,
|
||||
) {
|
||||
js, _ := jetstream.Prepare(process, &cfg.Matrix.JetStream)
|
||||
js, natsClient := jetstream.Prepare(process, &cfg.Matrix.JetStream)
|
||||
|
||||
syncProducer := &producers.SyncAPIProducer{
|
||||
JetStream: js,
|
||||
Topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputClientData),
|
||||
JetStream: js,
|
||||
TopicClientData: cfg.Matrix.JetStream.Prefixed(jetstream.OutputClientData),
|
||||
TopicReceiptEvent: cfg.Matrix.JetStream.Prefixed(jetstream.OutputReceiptEvent),
|
||||
TopicSendToDeviceEvent: cfg.Matrix.JetStream.Prefixed(jetstream.OutputSendToDeviceEvent),
|
||||
TopicTypingEvent: cfg.Matrix.JetStream.Prefixed(jetstream.OutputTypingEvent),
|
||||
TopicPresenceEvent: cfg.Matrix.JetStream.Prefixed(jetstream.OutputPresenceEvent),
|
||||
UserAPI: userAPI,
|
||||
ServerName: cfg.Matrix.ServerName,
|
||||
}
|
||||
|
||||
routing.Setup(
|
||||
router, synapseAdminRouter, cfg, eduInputAPI, rsAPI, asAPI,
|
||||
userAPI, federation,
|
||||
router, synapseAdminRouter, cfg, rsAPI, asAPI,
|
||||
userAPI, userDirectoryProvider, federation,
|
||||
syncProducer, transactionsCache, fsAPI, keyAPI,
|
||||
extRoomsProvider, mscCfg,
|
||||
extRoomsProvider, mscCfg, natsClient,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,32 +15,45 @@
|
|||
package producers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||
"github.com/matrix-org/dendrite/syncapi/types"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/nats-io/nats.go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// SyncAPIProducer produces events for the sync API server to consume
|
||||
type SyncAPIProducer struct {
|
||||
Topic string
|
||||
JetStream nats.JetStreamContext
|
||||
TopicClientData string
|
||||
TopicReceiptEvent string
|
||||
TopicSendToDeviceEvent string
|
||||
TopicTypingEvent string
|
||||
TopicPresenceEvent string
|
||||
JetStream nats.JetStreamContext
|
||||
ServerName gomatrixserverlib.ServerName
|
||||
UserAPI userapi.UserInternalAPI
|
||||
}
|
||||
|
||||
// SendData sends account data to the sync API server
|
||||
func (p *SyncAPIProducer) SendData(userID string, roomID string, dataType string, readMarker *eventutil.ReadMarkerJSON) error {
|
||||
func (p *SyncAPIProducer) SendData(userID string, roomID string, dataType string, readMarker *eventutil.ReadMarkerJSON, ignoredUsers *types.IgnoredUsers) error {
|
||||
m := &nats.Msg{
|
||||
Subject: p.Topic,
|
||||
Subject: p.TopicClientData,
|
||||
Header: nats.Header{},
|
||||
}
|
||||
m.Header.Set(jetstream.UserID, userID)
|
||||
|
||||
data := eventutil.AccountData{
|
||||
RoomID: roomID,
|
||||
Type: dataType,
|
||||
ReadMarker: readMarker,
|
||||
RoomID: roomID,
|
||||
Type: dataType,
|
||||
ReadMarker: readMarker,
|
||||
IgnoredUsers: ignoredUsers,
|
||||
}
|
||||
var err error
|
||||
m.Data, err = json.Marshal(data)
|
||||
|
|
@ -52,8 +65,130 @@ func (p *SyncAPIProducer) SendData(userID string, roomID string, dataType string
|
|||
"user_id": userID,
|
||||
"room_id": roomID,
|
||||
"data_type": dataType,
|
||||
}).Tracef("Producing to topic '%s'", p.Topic)
|
||||
}).Tracef("Producing to topic '%s'", p.TopicClientData)
|
||||
|
||||
_, err = p.JetStream.PublishMsg(m)
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *SyncAPIProducer) SendReceipt(
|
||||
ctx context.Context,
|
||||
userID, roomID, eventID, receiptType string, timestamp gomatrixserverlib.Timestamp,
|
||||
) error {
|
||||
m := &nats.Msg{
|
||||
Subject: p.TopicReceiptEvent,
|
||||
Header: nats.Header{},
|
||||
}
|
||||
m.Header.Set(jetstream.UserID, userID)
|
||||
m.Header.Set(jetstream.RoomID, roomID)
|
||||
m.Header.Set(jetstream.EventID, eventID)
|
||||
m.Header.Set("type", receiptType)
|
||||
m.Header.Set("timestamp", strconv.Itoa(int(timestamp)))
|
||||
|
||||
log.WithFields(log.Fields{}).Tracef("Producing to topic '%s'", p.TopicReceiptEvent)
|
||||
_, err := p.JetStream.PublishMsg(m, nats.Context(ctx))
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *SyncAPIProducer) SendToDevice(
|
||||
ctx context.Context, sender, userID, deviceID, eventType string,
|
||||
message interface{},
|
||||
) error {
|
||||
devices := []string{}
|
||||
_, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the event is targeted locally then we want to expand the wildcard
|
||||
// out into individual device IDs so that we can send them to each respective
|
||||
// device. If the event isn't targeted locally then we can't expand the
|
||||
// wildcard as we don't know about the remote devices, so instead we leave it
|
||||
// as-is, so that the federation sender can send it on with the wildcard intact.
|
||||
if domain == p.ServerName && deviceID == "*" {
|
||||
var res userapi.QueryDevicesResponse
|
||||
err = p.UserAPI.QueryDevices(context.TODO(), &userapi.QueryDevicesRequest{
|
||||
UserID: userID,
|
||||
}, &res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, dev := range res.Devices {
|
||||
devices = append(devices, dev.ID)
|
||||
}
|
||||
} else {
|
||||
devices = append(devices, deviceID)
|
||||
}
|
||||
|
||||
js, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"user_id": userID,
|
||||
"num_devices": len(devices),
|
||||
"type": eventType,
|
||||
}).Tracef("Producing to topic '%s'", p.TopicSendToDeviceEvent)
|
||||
for _, device := range devices {
|
||||
ote := &types.OutputSendToDeviceEvent{
|
||||
UserID: userID,
|
||||
DeviceID: device,
|
||||
SendToDeviceEvent: gomatrixserverlib.SendToDeviceEvent{
|
||||
Sender: sender,
|
||||
Type: eventType,
|
||||
Content: js,
|
||||
},
|
||||
}
|
||||
|
||||
eventJSON, err := json.Marshal(ote)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("sendToDevice failed json.Marshal")
|
||||
return err
|
||||
}
|
||||
m := &nats.Msg{
|
||||
Subject: p.TopicSendToDeviceEvent,
|
||||
Data: eventJSON,
|
||||
Header: nats.Header{},
|
||||
}
|
||||
m.Header.Set("sender", sender)
|
||||
m.Header.Set(jetstream.UserID, userID)
|
||||
if _, err = p.JetStream.PublishMsg(m, nats.Context(ctx)); err != nil {
|
||||
log.WithError(err).Error("sendToDevice failed t.Producer.SendMessage")
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *SyncAPIProducer) SendTyping(
|
||||
ctx context.Context, userID, roomID string, typing bool, timeoutMS int64,
|
||||
) error {
|
||||
m := &nats.Msg{
|
||||
Subject: p.TopicTypingEvent,
|
||||
Header: nats.Header{},
|
||||
}
|
||||
m.Header.Set(jetstream.UserID, userID)
|
||||
m.Header.Set(jetstream.RoomID, roomID)
|
||||
m.Header.Set("typing", strconv.FormatBool(typing))
|
||||
m.Header.Set("timeout_ms", strconv.Itoa(int(timeoutMS)))
|
||||
|
||||
_, err := p.JetStream.PublishMsg(m, nats.Context(ctx))
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *SyncAPIProducer) SendPresence(
|
||||
ctx context.Context, userID string, presence types.Presence, statusMsg *string,
|
||||
) error {
|
||||
m := nats.NewMsg(p.TopicPresenceEvent)
|
||||
m.Header.Set(jetstream.UserID, userID)
|
||||
m.Header.Set("presence", presence.String())
|
||||
if statusMsg != nil {
|
||||
m.Header.Set("status_msg", *statusMsg)
|
||||
}
|
||||
|
||||
m.Header.Set("last_active_ts", strconv.Itoa(int(gomatrixserverlib.AsTimestamp(time.Now()))))
|
||||
|
||||
_, err := p.JetStream.PublishMsg(m, nats.Context(ctx))
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ import (
|
|||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||
eduserverAPI "github.com/matrix-org/dendrite/eduserver/api"
|
||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/syncapi/types"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
|
||||
"github.com/matrix-org/util"
|
||||
|
|
@ -95,10 +95,10 @@ func SaveAccountData(
|
|||
}
|
||||
}
|
||||
|
||||
if dataType == "m.fully_read" {
|
||||
if dataType == "m.fully_read" || dataType == "m.push_rules" {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("Unable to set read marker"),
|
||||
JSON: jsonerror.Forbidden(fmt.Sprintf("Unable to modify %q using this API", dataType)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -127,8 +127,14 @@ func SaveAccountData(
|
|||
return util.ErrorResponse(err)
|
||||
}
|
||||
|
||||
var ignoredUsers *types.IgnoredUsers
|
||||
if dataType == "m.ignored_user_list" {
|
||||
ignoredUsers = &types.IgnoredUsers{}
|
||||
_ = json.Unmarshal(body, ignoredUsers)
|
||||
}
|
||||
|
||||
// TODO: user API should do this since it's account data
|
||||
if err := syncProducer.SendData(userID, roomID, dataType, nil); err != nil {
|
||||
if err := syncProducer.SendData(userID, roomID, dataType, nil, ignoredUsers); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("syncProducer.SendData failed")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
|
@ -146,7 +152,7 @@ type fullyReadEvent struct {
|
|||
// SaveReadMarker implements POST /rooms/{roomId}/read_markers
|
||||
func SaveReadMarker(
|
||||
req *http.Request,
|
||||
userAPI api.UserInternalAPI, rsAPI roomserverAPI.RoomserverInternalAPI, eduAPI eduserverAPI.EDUServerInputAPI,
|
||||
userAPI api.UserInternalAPI, rsAPI roomserverAPI.RoomserverInternalAPI,
|
||||
syncProducer *producers.SyncAPIProducer, device *api.Device, roomID string,
|
||||
) util.JSONResponse {
|
||||
// Verify that the user is a member of this room
|
||||
|
|
@ -185,14 +191,14 @@ func SaveReadMarker(
|
|||
return util.ErrorResponse(err)
|
||||
}
|
||||
|
||||
if err := syncProducer.SendData(device.UserID, roomID, "m.fully_read", &r); err != nil {
|
||||
if err := syncProducer.SendData(device.UserID, roomID, "m.fully_read", &r, nil); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("syncProducer.SendData failed")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
||||
// Handle the read receipt that may be included in the read marker
|
||||
if r.Read != "" {
|
||||
return SetReceipt(req, eduAPI, device, roomID, "m.read", r.Read)
|
||||
return SetReceipt(req, syncProducer, device, roomID, "m.read", r.Read)
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
|
|
|
|||
137
clientapi/routing/presence.go
Normal file
137
clientapi/routing/presence.go
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package routing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||
"github.com/matrix-org/dendrite/syncapi/types"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/nats-io/nats.go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type presenceReq struct {
|
||||
Presence string `json:"presence"`
|
||||
StatusMsg *string `json:"status_msg,omitempty"`
|
||||
}
|
||||
|
||||
func SetPresence(
|
||||
req *http.Request,
|
||||
cfg *config.ClientAPI,
|
||||
device *api.Device,
|
||||
producer *producers.SyncAPIProducer,
|
||||
userID string,
|
||||
) util.JSONResponse {
|
||||
if !cfg.Matrix.Presence.EnableOutbound {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: struct{}{},
|
||||
}
|
||||
}
|
||||
if device.UserID != userID {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("Unable to set presence for other user."),
|
||||
}
|
||||
}
|
||||
var presence presenceReq
|
||||
parseErr := httputil.UnmarshalJSONRequest(req, &presence)
|
||||
if parseErr != nil {
|
||||
return *parseErr
|
||||
}
|
||||
|
||||
presenceStatus, ok := types.PresenceFromString(presence.Presence)
|
||||
if !ok {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.Unknown(fmt.Sprintf("Unknown presence '%s'.", presence.Presence)),
|
||||
}
|
||||
}
|
||||
err := producer.SendPresence(req.Context(), userID, presenceStatus, presence.StatusMsg)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("failed to update presence")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.InternalServerError(),
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
func GetPresence(
|
||||
req *http.Request,
|
||||
device *api.Device,
|
||||
natsClient *nats.Conn,
|
||||
presenceTopic string,
|
||||
userID string,
|
||||
) util.JSONResponse {
|
||||
msg := nats.NewMsg(presenceTopic)
|
||||
msg.Header.Set(jetstream.UserID, userID)
|
||||
|
||||
presence, err := natsClient.RequestMsg(msg, time.Second*10)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("unable to get presence")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.InternalServerError(),
|
||||
}
|
||||
}
|
||||
|
||||
statusMsg := presence.Header.Get("status_msg")
|
||||
e := presence.Header.Get("error")
|
||||
if e != "" {
|
||||
log.Errorf("received error msg from nats: %s", e)
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: types.PresenceClientResponse{
|
||||
Presence: types.PresenceUnavailable.String(),
|
||||
},
|
||||
}
|
||||
}
|
||||
lastActive, err := strconv.Atoi(presence.Header.Get("last_active_ts"))
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.InternalServerError(),
|
||||
}
|
||||
}
|
||||
|
||||
p := types.PresenceInternal{LastActiveTS: gomatrixserverlib.Timestamp(lastActive)}
|
||||
currentlyActive := p.CurrentlyActive()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: types.PresenceClientResponse{
|
||||
CurrentlyActive: ¤tlyActive,
|
||||
LastActiveAgo: p.LastActiveAgo(),
|
||||
Presence: presence.Header.Get("presence"),
|
||||
StatusMsg: &statusMsg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -180,7 +180,7 @@ func SetAvatarURL(
|
|||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
||||
if err := api.SendEvents(req.Context(), rsAPI, api.KindNew, events, cfg.Matrix.ServerName, cfg.Matrix.ServerName, nil, false); err != nil {
|
||||
if err := api.SendEvents(req.Context(), rsAPI, api.KindNew, events, cfg.Matrix.ServerName, cfg.Matrix.ServerName, nil, true); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("SendEvents failed")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,21 +19,20 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
|
||||
"github.com/matrix-org/dendrite/eduserver/api"
|
||||
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func SetReceipt(req *http.Request, eduAPI api.EDUServerInputAPI, device *userapi.Device, roomId, receiptType, eventId string) util.JSONResponse {
|
||||
func SetReceipt(req *http.Request, syncProducer *producers.SyncAPIProducer, device *userapi.Device, roomID, receiptType, eventID string) util.JSONResponse {
|
||||
timestamp := gomatrixserverlib.AsTimestamp(time.Now())
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"roomId": roomId,
|
||||
"roomID": roomID,
|
||||
"receiptType": receiptType,
|
||||
"eventId": eventId,
|
||||
"eventID": eventID,
|
||||
"userId": device.UserID,
|
||||
"timestamp": timestamp,
|
||||
}).Debug("Setting receipt")
|
||||
|
|
@ -43,7 +42,7 @@ func SetReceipt(req *http.Request, eduAPI api.EDUServerInputAPI, device *userapi
|
|||
return util.MessageResponse(400, fmt.Sprintf("receipt type must be m.read not '%s'", receiptType))
|
||||
}
|
||||
|
||||
if err := api.SendReceipt(req.Context(), eduAPI, device.UserID, roomId, eventId, receiptType, timestamp); err != nil {
|
||||
if err := syncProducer.SendReceipt(req.Context(), device.UserID, roomID, eventID, receiptType, timestamp); err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import (
|
|||
|
||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/tokens"
|
||||
|
|
@ -63,11 +64,6 @@ const (
|
|||
sessionIDLength = 24
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Register prometheus metrics. They must be registered to be exposed.
|
||||
prometheus.MustRegister(amtRegUsers)
|
||||
}
|
||||
|
||||
// sessionsDict keeps track of completed auth stages for each session.
|
||||
// It shouldn't be passed by value because it contains a mutex.
|
||||
type sessionsDict struct {
|
||||
|
|
@ -525,22 +521,37 @@ func Register(
|
|||
userAPI userapi.UserRegisterAPI,
|
||||
cfg *config.ClientAPI,
|
||||
) util.JSONResponse {
|
||||
defer req.Body.Close() // nolint: errcheck
|
||||
reqBody, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.NotJSON("Unable to read request body"),
|
||||
}
|
||||
}
|
||||
|
||||
var r registerRequest
|
||||
resErr := httputil.UnmarshalJSONRequest(req, &r)
|
||||
if resErr != nil {
|
||||
sessionID := gjson.GetBytes(reqBody, "auth.session").String()
|
||||
if sessionID == "" {
|
||||
// Generate a new, random session ID
|
||||
sessionID = util.RandomString(sessionIDLength)
|
||||
} else if data, ok := sessions.getParams(sessionID); ok {
|
||||
// Use the parameters from the session as our defaults.
|
||||
// Some of these might end up being overwritten if the
|
||||
// values are specified again in the request body.
|
||||
r.Username = data.Username
|
||||
r.Password = data.Password
|
||||
r.DeviceID = data.DeviceID
|
||||
r.InitialDisplayName = data.InitialDisplayName
|
||||
r.InhibitLogin = data.InhibitLogin
|
||||
}
|
||||
if resErr := httputil.UnmarshalJSON(reqBody, &r); resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
if req.URL.Query().Get("kind") == "guest" {
|
||||
return handleGuestRegistration(req, r, cfg, userAPI)
|
||||
}
|
||||
|
||||
// Retrieve or generate the sessionID
|
||||
sessionID := r.Auth.Session
|
||||
if sessionID == "" {
|
||||
// Generate a new, random session ID
|
||||
sessionID = util.RandomString(sessionIDLength)
|
||||
}
|
||||
|
||||
// Don't allow numeric usernames less than MAX_INT64.
|
||||
if _, err := strconv.ParseInt(r.Username, 10, 64); err == nil {
|
||||
return util.JSONResponse{
|
||||
|
|
@ -568,7 +579,7 @@ func Register(
|
|||
case r.Type == authtypes.LoginTypeApplicationService && accessTokenErr == nil:
|
||||
// Spec-compliant case (the access_token is specified and the login type
|
||||
// is correctly set, so it's an appservice registration)
|
||||
if resErr = validateApplicationServiceUsername(r.Username); resErr != nil {
|
||||
if resErr := validateApplicationServiceUsername(r.Username); resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
case accessTokenErr == nil:
|
||||
|
|
@ -581,11 +592,11 @@ func Register(
|
|||
default:
|
||||
// Spec-compliant case (neither the access_token nor the login type are
|
||||
// specified, so it's a normal user registration)
|
||||
if resErr = validateUsername(r.Username); resErr != nil {
|
||||
if resErr := validateUsername(r.Username); resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
}
|
||||
if resErr = validatePassword(r.Password); resErr != nil {
|
||||
if resErr := validatePassword(r.Password); resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
|
||||
|
|
@ -835,24 +846,17 @@ func completeRegistration(
|
|||
}
|
||||
}()
|
||||
|
||||
if data, ok := sessions.getParams(sessionID); ok {
|
||||
username = data.Username
|
||||
password = data.Password
|
||||
deviceID = data.DeviceID
|
||||
displayName = data.InitialDisplayName
|
||||
inhibitLogin = data.InhibitLogin
|
||||
}
|
||||
if username == "" {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("missing username"),
|
||||
JSON: jsonerror.MissingArgument("Missing username"),
|
||||
}
|
||||
}
|
||||
// Blank passwords are only allowed by registered application services
|
||||
if password == "" && appserviceID == "" {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("missing password"),
|
||||
JSON: jsonerror.MissingArgument("Missing password"),
|
||||
}
|
||||
}
|
||||
var accRes userapi.PerformAccountCreationResponse
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ func PutTag(
|
|||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
||||
if err = syncProducer.SendData(userID, roomID, "m.tag", nil); err != nil {
|
||||
if err = syncProducer.SendData(userID, roomID, "m.tag", nil, nil); err != nil {
|
||||
logrus.WithError(err).Error("Failed to send m.tag account data update to syncapi")
|
||||
}
|
||||
|
||||
|
|
@ -151,7 +151,7 @@ func DeleteTag(
|
|||
}
|
||||
|
||||
// TODO: user API should do this since it's account data
|
||||
if err := syncProducer.SendData(userID, roomID, "m.tag", nil); err != nil {
|
||||
if err := syncProducer.SendData(userID, roomID, "m.tag", nil, nil); err != nil {
|
||||
logrus.WithError(err).Error("Failed to send m.tag account data update to syncapi")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,16 +26,18 @@ import (
|
|||
clientutil "github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||
eduServerAPI "github.com/matrix-org/dendrite/eduserver/api"
|
||||
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
||||
"github.com/matrix-org/dendrite/internal/httputil"
|
||||
"github.com/matrix-org/dendrite/internal/transactions"
|
||||
keyserverAPI "github.com/matrix-org/dendrite/keyserver/api"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
|
@ -47,18 +49,20 @@ import (
|
|||
// nolint: gocyclo
|
||||
func Setup(
|
||||
publicAPIMux, synapseAdminRouter *mux.Router, cfg *config.ClientAPI,
|
||||
eduAPI eduServerAPI.EDUServerInputAPI,
|
||||
rsAPI roomserverAPI.RoomserverInternalAPI,
|
||||
asAPI appserviceAPI.AppServiceQueryAPI,
|
||||
userAPI userapi.UserInternalAPI,
|
||||
userDirectoryProvider userapi.UserDirectoryProvider,
|
||||
federation *gomatrixserverlib.FederationClient,
|
||||
syncProducer *producers.SyncAPIProducer,
|
||||
transactionsCache *transactions.Cache,
|
||||
federationSender federationAPI.FederationInternalAPI,
|
||||
keyAPI keyserverAPI.KeyInternalAPI,
|
||||
extRoomsProvider api.ExtraPublicRoomsProvider,
|
||||
mscCfg *config.MSCs,
|
||||
mscCfg *config.MSCs, natsClient *nats.Conn,
|
||||
) {
|
||||
prometheus.MustRegister(amtRegUsers, sendEventDuration)
|
||||
|
||||
rateLimits := httputil.NewRateLimits(&cfg.RateLimiting)
|
||||
userInteractiveAuth := auth.NewUserInteractive(userAPI, cfg)
|
||||
|
||||
|
|
@ -466,7 +470,7 @@ func Setup(
|
|||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
return SendTyping(req, device, vars["roomID"], vars["userID"], eduAPI, rsAPI)
|
||||
return SendTyping(req, device, vars["roomID"], vars["userID"], rsAPI, syncProducer)
|
||||
}),
|
||||
).Methods(http.MethodPut, http.MethodOptions)
|
||||
v3mux.Handle("/rooms/{roomID}/redact/{eventID}",
|
||||
|
|
@ -495,7 +499,7 @@ func Setup(
|
|||
return util.ErrorResponse(err)
|
||||
}
|
||||
txnID := vars["txnID"]
|
||||
return SendToDevice(req, device, eduAPI, transactionsCache, vars["eventType"], &txnID)
|
||||
return SendToDevice(req, device, syncProducer, transactionsCache, vars["eventType"], &txnID)
|
||||
}),
|
||||
).Methods(http.MethodPut, http.MethodOptions)
|
||||
|
||||
|
|
@ -509,7 +513,7 @@ func Setup(
|
|||
return util.ErrorResponse(err)
|
||||
}
|
||||
txnID := vars["txnID"]
|
||||
return SendToDevice(req, device, eduAPI, transactionsCache, vars["eventType"], &txnID)
|
||||
return SendToDevice(req, device, syncProducer, transactionsCache, vars["eventType"], &txnID)
|
||||
}),
|
||||
).Methods(http.MethodPut, http.MethodOptions)
|
||||
|
||||
|
|
@ -780,20 +784,6 @@ func Setup(
|
|||
}),
|
||||
).Methods(http.MethodPost, http.MethodOptions)
|
||||
|
||||
// Element logs get flooded unless this is handled
|
||||
v3mux.Handle("/presence/{userID}/status",
|
||||
httputil.MakeExternalAPI("presence", func(req *http.Request) util.JSONResponse {
|
||||
if r := rateLimits.Limit(req); r != nil {
|
||||
return *r
|
||||
}
|
||||
// TODO: Set presence (probably the responsibility of a presence server not clientapi)
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: struct{}{},
|
||||
}
|
||||
}),
|
||||
).Methods(http.MethodPut, http.MethodOptions)
|
||||
|
||||
v3mux.Handle("/voip/turnServer",
|
||||
httputil.MakeAuthAPI("turn_server", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
if r := rateLimits.Limit(req); r != nil {
|
||||
|
|
@ -904,6 +894,7 @@ func Setup(
|
|||
device,
|
||||
userAPI,
|
||||
rsAPI,
|
||||
userDirectoryProvider,
|
||||
cfg.Matrix.ServerName,
|
||||
postContent.SearchString,
|
||||
postContent.Limit,
|
||||
|
|
@ -940,7 +931,7 @@ func Setup(
|
|||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
return SaveReadMarker(req, userAPI, rsAPI, eduAPI, syncProducer, device, vars["roomID"])
|
||||
return SaveReadMarker(req, userAPI, rsAPI, syncProducer, device, vars["roomID"])
|
||||
}),
|
||||
).Methods(http.MethodPost, http.MethodOptions)
|
||||
|
||||
|
|
@ -957,6 +948,16 @@ func Setup(
|
|||
}),
|
||||
).Methods(http.MethodPost, http.MethodOptions)
|
||||
|
||||
v3mux.Handle("/rooms/{roomID}/upgrade",
|
||||
httputil.MakeAuthAPI("rooms_upgrade", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
return UpgradeRoom(req, device, cfg, vars["roomID"], userAPI, rsAPI, asAPI)
|
||||
}),
|
||||
).Methods(http.MethodPost, http.MethodOptions)
|
||||
|
||||
v3mux.Handle("/devices",
|
||||
httputil.MakeAuthAPI("get_devices", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
return GetDevicesByLocalpart(req, userAPI, device)
|
||||
|
|
@ -1295,7 +1296,25 @@ func Setup(
|
|||
return util.ErrorResponse(err)
|
||||
}
|
||||
|
||||
return SetReceipt(req, eduAPI, device, vars["roomId"], vars["receiptType"], vars["eventId"])
|
||||
return SetReceipt(req, syncProducer, device, vars["roomId"], vars["receiptType"], vars["eventId"])
|
||||
}),
|
||||
).Methods(http.MethodPost, http.MethodOptions)
|
||||
v3mux.Handle("/presence/{userId}/status",
|
||||
httputil.MakeAuthAPI("set_presence", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
return SetPresence(req, cfg, device, syncProducer, vars["userId"])
|
||||
}),
|
||||
).Methods(http.MethodPut, http.MethodOptions)
|
||||
v3mux.Handle("/presence/{userId}/status",
|
||||
httputil.MakeAuthAPI("get_presence", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
return GetPresence(req, device, natsClient, cfg.Matrix.JetStream.Prefixed(jetstream.RequestPresence), vars["userId"])
|
||||
}),
|
||||
).Methods(http.MethodGet, http.MethodOptions)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,10 +46,6 @@ var (
|
|||
userRoomSendMutexes sync.Map // (roomID+userID) -> mutex. mutexes to ensure correct ordering of sendEvents
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(sendEventDuration)
|
||||
}
|
||||
|
||||
var sendEventDuration = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: "dendrite",
|
||||
|
|
@ -272,5 +268,24 @@ func generateSendEvent(
|
|||
JSON: jsonerror.Forbidden(err.Error()), // TODO: Is this error string comprehensible to the client?
|
||||
}
|
||||
}
|
||||
|
||||
// User should not be able to send a tombstone event to the same room.
|
||||
if e.Type() == "m.room.tombstone" {
|
||||
content := make(map[string]interface{})
|
||||
if err = json.Unmarshal(e.Content(), &content); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("Cannot unmarshal the event content.")
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("Cannot unmarshal the event content."),
|
||||
}
|
||||
}
|
||||
if content["replacement_room"] == e.RoomID() {
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.InvalidParam("Cannot send tombstone event that points to the same room."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return e.Event, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,17 +18,17 @@ import (
|
|||
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/eduserver/api"
|
||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||
"github.com/matrix-org/dendrite/internal/transactions"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
// SendToDevice handles PUT /_matrix/client/r0/sendToDevice/{eventType}/{txnId}
|
||||
// sends the device events to the EDU Server
|
||||
// sends the device events to the syncapi & federationsender
|
||||
func SendToDevice(
|
||||
req *http.Request, device *userapi.Device,
|
||||
eduAPI api.EDUServerInputAPI,
|
||||
syncProducer *producers.SyncAPIProducer,
|
||||
txnCache *transactions.Cache,
|
||||
eventType string, txnID *string,
|
||||
) util.JSONResponse {
|
||||
|
|
@ -48,8 +48,8 @@ func SendToDevice(
|
|||
|
||||
for userID, byUser := range httpReq.Messages {
|
||||
for deviceID, message := range byUser {
|
||||
if err := api.SendToDevice(
|
||||
req.Context(), eduAPI, device.UserID, userID, deviceID, eventType, message,
|
||||
if err := syncProducer.SendToDevice(
|
||||
req.Context(), device.UserID, userID, deviceID, eventType, message,
|
||||
); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("eduProducer.SendToDevice failed")
|
||||
return jsonerror.InternalServerError()
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import (
|
|||
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/eduserver/api"
|
||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/util"
|
||||
|
|
@ -32,9 +32,8 @@ type typingContentJSON struct {
|
|||
// sends the typing events to client API typingProducer
|
||||
func SendTyping(
|
||||
req *http.Request, device *userapi.Device, roomID string,
|
||||
userID string,
|
||||
eduAPI api.EDUServerInputAPI,
|
||||
rsAPI roomserverAPI.RoomserverInternalAPI,
|
||||
userID string, rsAPI roomserverAPI.RoomserverInternalAPI,
|
||||
syncProducer *producers.SyncAPIProducer,
|
||||
) util.JSONResponse {
|
||||
if device.UserID != userID {
|
||||
return util.JSONResponse{
|
||||
|
|
@ -56,9 +55,7 @@ func SendTyping(
|
|||
return *resErr
|
||||
}
|
||||
|
||||
if err := api.SendTyping(
|
||||
req.Context(), eduAPI, userID, roomID, r.Typing, r.Timeout,
|
||||
); err != nil {
|
||||
if err := syncProducer.SendTyping(req.Context(), userID, roomID, r.Typing, r.Timeout); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("eduProducer.Send failed")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
|
|
|||
92
clientapi/routing/upgrade_room.go
Normal file
92
clientapi/routing/upgrade_room.go
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package routing
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/roomserver/version"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
type upgradeRoomRequest struct {
|
||||
NewVersion string `json:"new_version"`
|
||||
}
|
||||
|
||||
type upgradeRoomResponse struct {
|
||||
ReplacementRoom string `json:"replacement_room"`
|
||||
}
|
||||
|
||||
// UpgradeRoom implements /upgrade
|
||||
func UpgradeRoom(
|
||||
req *http.Request, device *userapi.Device,
|
||||
cfg *config.ClientAPI,
|
||||
roomID string, profileAPI userapi.UserProfileAPI,
|
||||
rsAPI roomserverAPI.RoomserverInternalAPI,
|
||||
asAPI appserviceAPI.AppServiceQueryAPI,
|
||||
) util.JSONResponse {
|
||||
var r upgradeRoomRequest
|
||||
if rErr := httputil.UnmarshalJSONRequest(req, &r); rErr != nil {
|
||||
return *rErr
|
||||
}
|
||||
|
||||
// Validate that the room version is supported
|
||||
if _, err := version.SupportedRoomVersion(gomatrixserverlib.RoomVersion(r.NewVersion)); err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.UnsupportedRoomVersion("This server does not support that room version"),
|
||||
}
|
||||
}
|
||||
|
||||
upgradeReq := roomserverAPI.PerformRoomUpgradeRequest{
|
||||
UserID: device.UserID,
|
||||
RoomID: roomID,
|
||||
RoomVersion: gomatrixserverlib.RoomVersion(r.NewVersion),
|
||||
}
|
||||
upgradeResp := roomserverAPI.PerformRoomUpgradeResponse{}
|
||||
|
||||
rsAPI.PerformRoomUpgrade(req.Context(), &upgradeReq, &upgradeResp)
|
||||
|
||||
if upgradeResp.Error != nil {
|
||||
if upgradeResp.Error.Code == roomserverAPI.PerformErrorNoRoom {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound("Room does not exist"),
|
||||
}
|
||||
} else if upgradeResp.Error.Code == roomserverAPI.PerformErrorNotAllowed {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden(upgradeResp.Error.Msg),
|
||||
}
|
||||
} else {
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: upgradeRoomResponse{
|
||||
ReplacementRoom: upgradeResp.NewRoomID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ package routing
|
|||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
|
|
@ -35,6 +36,7 @@ func SearchUserDirectory(
|
|||
device *userapi.Device,
|
||||
userAPI userapi.UserInternalAPI,
|
||||
rsAPI api.RoomserverInternalAPI,
|
||||
provider userapi.UserDirectoryProvider,
|
||||
serverName gomatrixserverlib.ServerName,
|
||||
searchString string,
|
||||
limit int,
|
||||
|
|
@ -50,13 +52,12 @@ func SearchUserDirectory(
|
|||
}
|
||||
|
||||
// First start searching local users.
|
||||
|
||||
userReq := &userapi.QuerySearchProfilesRequest{
|
||||
SearchString: searchString,
|
||||
Limit: limit,
|
||||
}
|
||||
userRes := &userapi.QuerySearchProfilesResponse{}
|
||||
if err := userAPI.QuerySearchProfiles(ctx, userReq, userRes); err != nil {
|
||||
if err := provider.QuerySearchProfiles(ctx, userReq, userRes); err != nil {
|
||||
errRes := util.ErrorResponse(fmt.Errorf("userAPI.QuerySearchProfiles: %w", err))
|
||||
return &errRes
|
||||
}
|
||||
|
|
@ -67,7 +68,12 @@ func SearchUserDirectory(
|
|||
break
|
||||
}
|
||||
|
||||
userID := fmt.Sprintf("@%s:%s", user.Localpart, serverName)
|
||||
var userID string
|
||||
if user.ServerName != "" {
|
||||
userID = fmt.Sprintf("@%s:%s", user.Localpart, user.ServerName)
|
||||
} else {
|
||||
userID = fmt.Sprintf("@%s:%s", user.Localpart, serverName)
|
||||
}
|
||||
if _, ok := results[userID]; !ok {
|
||||
results[userID] = authtypes.FullyQualifiedProfile{
|
||||
UserID: userID,
|
||||
|
|
@ -87,7 +93,7 @@ func SearchUserDirectory(
|
|||
Limit: limit - len(results),
|
||||
}
|
||||
stateRes := &api.QueryKnownUsersResponse{}
|
||||
if err := rsAPI.QueryKnownUsers(ctx, stateReq, stateRes); err != nil {
|
||||
if err := rsAPI.QueryKnownUsers(ctx, stateReq, stateRes); err != nil && err != sql.ErrNoRows {
|
||||
errRes := util.ErrorResponse(fmt.Errorf("rsAPI.QueryKnownUsers: %w", err))
|
||||
return &errRes
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,156 +0,0 @@
|
|||
// Copyright 2017 Vector Creations Ltd
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const usage = `Usage: %s
|
||||
|
||||
Create a single endpoint URL which clients can be pointed at.
|
||||
|
||||
The client-server API in Dendrite is split across multiple processes
|
||||
which listen on multiple ports. You cannot point a Matrix client at
|
||||
any of those ports, as there will be unimplemented functionality.
|
||||
In addition, all client-server API processes start with the additional
|
||||
path prefix '/api', which Matrix clients will be unaware of.
|
||||
|
||||
This tool will proxy requests for all client-server URLs and forward
|
||||
them to their respective process. It will also add the '/api' path
|
||||
prefix to incoming requests.
|
||||
|
||||
THIS TOOL IS FOR TESTING AND NOT INTENDED FOR PRODUCTION USE.
|
||||
|
||||
Arguments:
|
||||
|
||||
`
|
||||
|
||||
var (
|
||||
syncServerURL = flag.String("sync-api-server-url", "", "The base URL of the listening 'dendrite-sync-api-server' process. E.g. 'http://localhost:4200'")
|
||||
clientAPIURL = flag.String("client-api-server-url", "", "The base URL of the listening 'dendrite-client-api-server' process. E.g. 'http://localhost:4321'")
|
||||
mediaAPIURL = flag.String("media-api-server-url", "", "The base URL of the listening 'dendrite-media-api-server' process. E.g. 'http://localhost:7779'")
|
||||
bindAddress = flag.String("bind-address", ":8008", "The listening port for the proxy.")
|
||||
certFile = flag.String("tls-cert", "", "The PEM formatted X509 certificate to use for TLS")
|
||||
keyFile = flag.String("tls-key", "", "The PEM private key to use for TLS")
|
||||
)
|
||||
|
||||
func makeProxy(targetURL string) (*httputil.ReverseProxy, error) {
|
||||
targetURL = strings.TrimSuffix(targetURL, "/")
|
||||
|
||||
// Check that we can parse the URL.
|
||||
_, err := url.Parse(targetURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &httputil.ReverseProxy{
|
||||
Director: func(req *http.Request) {
|
||||
// URL.Path() removes the % escaping from the path.
|
||||
// The % encoding will be added back when the url is encoded
|
||||
// when the request is forwarded.
|
||||
// This means that we will lose any unessecary escaping from the URL.
|
||||
// Pratically this means that any distinction between '%2F' and '/'
|
||||
// in the URL will be lost by the time it reaches the target.
|
||||
path := req.URL.Path
|
||||
log.WithFields(log.Fields{
|
||||
"path": path,
|
||||
"url": targetURL,
|
||||
"method": req.Method,
|
||||
}).Print("proxying request")
|
||||
newURL, err := url.Parse(targetURL)
|
||||
// Set the path separately as we need to preserve '#' characters
|
||||
// that would otherwise be interpreted as being the start of a URL
|
||||
// fragment.
|
||||
newURL.Path += path
|
||||
if err != nil {
|
||||
// We already checked that we can parse the URL
|
||||
// So this shouldn't ever get hit.
|
||||
panic(err)
|
||||
}
|
||||
// Copy the query parameters from the request.
|
||||
newURL.RawQuery = req.URL.RawQuery
|
||||
req.URL = newURL
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, usage, os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *syncServerURL == "" {
|
||||
flag.Usage()
|
||||
fmt.Fprintln(os.Stderr, "no --sync-api-server-url specified.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if *clientAPIURL == "" {
|
||||
flag.Usage()
|
||||
fmt.Fprintln(os.Stderr, "no --client-api-server-url specified.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if *mediaAPIURL == "" {
|
||||
flag.Usage()
|
||||
fmt.Fprintln(os.Stderr, "no --media-api-server-url specified.")
|
||||
os.Exit(1)
|
||||
}
|
||||
syncProxy, err := makeProxy(*syncServerURL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
clientProxy, err := makeProxy(*clientAPIURL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
mediaProxy, err := makeProxy(*mediaAPIURL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
http.Handle("/_matrix/client/r0/sync", syncProxy)
|
||||
http.Handle("/_matrix/media/v1/", mediaProxy)
|
||||
http.Handle("/", clientProxy)
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: *bindAddress,
|
||||
ReadTimeout: 1 * time.Minute, // how long we wait for the client to send the entire request (after connection accept)
|
||||
WriteTimeout: 5 * time.Minute, // how long the proxy has to write the full response
|
||||
}
|
||||
|
||||
fmt.Println("Proxying requests to:")
|
||||
fmt.Println(" /_matrix/client/r0/sync => ", *syncServerURL+"/api/_matrix/client/r0/sync")
|
||||
fmt.Println(" /_matrix/media/v1 => ", *mediaAPIURL+"/api/_matrix/media/v1")
|
||||
fmt.Println(" /* => ", *clientAPIURL+"/api/*")
|
||||
fmt.Println("Listening on ", *bindAddress)
|
||||
if *certFile != "" && *keyFile != "" {
|
||||
panic(srv.ListenAndServeTLS(*certFile, *keyFile))
|
||||
} else {
|
||||
panic(srv.ListenAndServe())
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +29,6 @@ import (
|
|||
p2pdisc "github.com/libp2p/go-libp2p/p2p/discovery"
|
||||
"github.com/matrix-org/dendrite/appservice"
|
||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/embed"
|
||||
"github.com/matrix-org/dendrite/eduserver"
|
||||
"github.com/matrix-org/dendrite/federationapi"
|
||||
"github.com/matrix-org/dendrite/internal/httputil"
|
||||
"github.com/matrix-org/dendrite/keyserver"
|
||||
|
|
@ -40,8 +39,6 @@ import (
|
|||
"github.com/matrix-org/dendrite/userapi"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
|
||||
"github.com/matrix-org/dendrite/eduserver/cache"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
|
@ -152,9 +149,6 @@ func main() {
|
|||
userAPI := userapi.NewInternalAPI(&base.Base, accountDB, &cfg.UserAPI, nil, keyAPI, rsAPI, base.Base.PushGatewayHTTPClient())
|
||||
keyAPI.SetUserAPI(userAPI)
|
||||
|
||||
eduInputAPI := eduserver.NewInternalAPI(
|
||||
&base.Base, cache.New(), userAPI,
|
||||
)
|
||||
asAPI := appservice.NewInternalAPI(&base.Base, userAPI, rsAPI)
|
||||
rsAPI.SetAppserviceAPI(asAPI)
|
||||
fsAPI := federationapi.NewInternalAPI(
|
||||
|
|
@ -180,7 +174,6 @@ func main() {
|
|||
KeyRing: keyRing,
|
||||
|
||||
AppserviceAPI: asAPI,
|
||||
EDUInternalAPI: eduInputAPI,
|
||||
FederationAPI: fsAPI,
|
||||
RoomserverAPI: rsAPI,
|
||||
UserAPI: userAPI,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,17 @@
|
|||
// 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 conn
|
||||
|
||||
import (
|
||||
|
|
@ -53,21 +67,22 @@ func (y *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||
}
|
||||
|
||||
func createTransport(s *pineconeSessions.Sessions) *http.Transport {
|
||||
proto := s.Protocol("matrix")
|
||||
tr := &http.Transport{
|
||||
DisableKeepAlives: false,
|
||||
Dial: s.Dial,
|
||||
DialContext: s.DialContext,
|
||||
DialTLS: s.DialTLS,
|
||||
DialTLSContext: s.DialTLSContext,
|
||||
Dial: proto.Dial,
|
||||
DialContext: proto.DialContext,
|
||||
DialTLS: proto.DialTLS,
|
||||
DialTLSContext: proto.DialTLSContext,
|
||||
}
|
||||
tr.RegisterProtocol(
|
||||
"matrix", &RoundTripper{
|
||||
inner: &http.Transport{
|
||||
DisableKeepAlives: false,
|
||||
Dial: s.Dial,
|
||||
DialContext: s.DialContext,
|
||||
DialTLS: s.DialTLS,
|
||||
DialTLSContext: s.DialTLSContext,
|
||||
Dial: proto.Dial,
|
||||
DialContext: proto.DialContext,
|
||||
DialTLS: proto.DialTLS,
|
||||
DialTLSContext: proto.DialTLSContext,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,17 @@
|
|||
// 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 conn
|
||||
|
||||
import (
|
||||
|
|
|
|||
21
cmd/dendrite-demo-pinecone/defaults/defaults.go
Normal file
21
cmd/dendrite-demo-pinecone/defaults/defaults.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// 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 defaults
|
||||
|
||||
import "github.com/matrix-org/gomatrixserverlib"
|
||||
|
||||
var DefaultServerNames = map[gomatrixserverlib.ServerName]struct{}{
|
||||
"3bf0258d23c60952639cc4c69c71d1508a7d43a0475d9000ff900a1848411ec7": {},
|
||||
}
|
||||
|
|
@ -1,3 +1,17 @@
|
|||
// 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.
|
||||
|
||||
//go:build elementweb
|
||||
// +build elementweb
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,17 @@
|
|||
// 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.
|
||||
|
||||
//go:build !elementweb
|
||||
// +build !elementweb
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
// 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.
|
||||
|
|
@ -22,11 +22,9 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
|
@ -35,11 +33,9 @@ import (
|
|||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/conn"
|
||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/embed"
|
||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/rooms"
|
||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/users"
|
||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing"
|
||||
"github.com/matrix-org/dendrite/eduserver"
|
||||
"github.com/matrix-org/dendrite/eduserver/cache"
|
||||
"github.com/matrix-org/dendrite/federationapi"
|
||||
"github.com/matrix-org/dendrite/federationapi/api"
|
||||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/internal/httputil"
|
||||
"github.com/matrix-org/dendrite/keyserver"
|
||||
|
|
@ -50,6 +46,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/userapi"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
|
||||
pineconeConnections "github.com/matrix-org/pinecone/connections"
|
||||
pineconeMulticast "github.com/matrix-org/pinecone/multicast"
|
||||
pineconeRouter "github.com/matrix-org/pinecone/router"
|
||||
pineconeSessions "github.com/matrix-org/pinecone/sessions"
|
||||
|
|
@ -92,8 +89,14 @@ func main() {
|
|||
pk = sk.Public().(ed25519.PublicKey)
|
||||
}
|
||||
|
||||
logger := log.New(os.Stdout, "", 0)
|
||||
pRouter := pineconeRouter.NewRouter(logger, sk, false)
|
||||
pRouter := pineconeRouter.NewRouter(logrus.WithField("pinecone", "router"), sk, false)
|
||||
pQUIC := pineconeSessions.NewSessions(logrus.WithField("pinecone", "sessions"), pRouter, []string{"matrix"})
|
||||
pMulticast := pineconeMulticast.NewMulticast(logrus.WithField("pinecone", "multicast"), pRouter)
|
||||
pManager := pineconeConnections.NewConnectionManager(pRouter)
|
||||
pMulticast.Start()
|
||||
if instancePeer != nil && *instancePeer != "" {
|
||||
pManager.AddPeer(*instancePeer)
|
||||
}
|
||||
|
||||
go func() {
|
||||
listener, err := net.Listen("tcp", *instanceListen)
|
||||
|
|
@ -123,36 +126,6 @@ func main() {
|
|||
}
|
||||
}()
|
||||
|
||||
pQUIC := pineconeSessions.NewSessions(logger, pRouter)
|
||||
pMulticast := pineconeMulticast.NewMulticast(logger, pRouter)
|
||||
pMulticast.Start()
|
||||
|
||||
connectToStaticPeer := func() {
|
||||
connected := map[string]bool{} // URI -> connected?
|
||||
for _, uri := range strings.Split(*instancePeer, ",") {
|
||||
connected[strings.TrimSpace(uri)] = false
|
||||
}
|
||||
attempt := func() {
|
||||
for k := range connected {
|
||||
connected[k] = false
|
||||
}
|
||||
for _, info := range pRouter.Peers() {
|
||||
connected[info.URI] = true
|
||||
}
|
||||
for k, online := range connected {
|
||||
if !online {
|
||||
if err := conn.ConnectToPeer(pRouter, k); err != nil {
|
||||
logrus.WithError(err).Error("Failed to connect to static peer")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for {
|
||||
attempt()
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
}
|
||||
|
||||
cfg := &config.Dendrite{}
|
||||
cfg.Defaults(true)
|
||||
cfg.Global.ServerName = gomatrixserverlib.ServerName(hex.EncodeToString(pk))
|
||||
|
|
@ -190,14 +163,13 @@ func main() {
|
|||
userAPI := userapi.NewInternalAPI(base, accountDB, &cfg.UserAPI, nil, keyAPI, rsAPI, base.PushGatewayHTTPClient())
|
||||
keyAPI.SetUserAPI(userAPI)
|
||||
|
||||
eduInputAPI := eduserver.NewInternalAPI(
|
||||
base, cache.New(), userAPI,
|
||||
)
|
||||
|
||||
asAPI := appservice.NewInternalAPI(base, userAPI, rsAPI)
|
||||
|
||||
rsComponent.SetFederationAPI(fsAPI, keyRing)
|
||||
|
||||
userProvider := users.NewPineconeUserProvider(pRouter, pQUIC, userAPI, federation)
|
||||
roomProvider := rooms.NewPineconeRoomProvider(pRouter, pQUIC, fsAPI, federation)
|
||||
|
||||
monolith := setup.Monolith{
|
||||
Config: base.Cfg,
|
||||
AccountDB: accountDB,
|
||||
|
|
@ -205,13 +177,13 @@ func main() {
|
|||
FedClient: federation,
|
||||
KeyRing: keyRing,
|
||||
|
||||
AppserviceAPI: asAPI,
|
||||
EDUInternalAPI: eduInputAPI,
|
||||
FederationAPI: fsAPI,
|
||||
RoomserverAPI: rsAPI,
|
||||
UserAPI: userAPI,
|
||||
KeyAPI: keyAPI,
|
||||
ExtPublicRoomsProvider: rooms.NewPineconeRoomProvider(pRouter, pQUIC, fsAPI, federation),
|
||||
AppserviceAPI: asAPI,
|
||||
FederationAPI: fsAPI,
|
||||
RoomserverAPI: rsAPI,
|
||||
UserAPI: userAPI,
|
||||
KeyAPI: keyAPI,
|
||||
ExtPublicRoomsProvider: roomProvider,
|
||||
ExtUserDirectoryProvider: userProvider,
|
||||
}
|
||||
monolith.AddAllPublicRoutes(
|
||||
base.ProcessContext,
|
||||
|
|
@ -247,13 +219,16 @@ func main() {
|
|||
logrus.WithError(err).Error("Failed to connect WebSocket peer to Pinecone switch")
|
||||
}
|
||||
})
|
||||
httpRouter.HandleFunc("/pinecone", pRouter.ManholeHandler)
|
||||
embed.Embed(httpRouter, *instancePort, "Pinecone Demo")
|
||||
|
||||
pMux := mux.NewRouter().SkipClean(true).UseEncodedPath()
|
||||
pMux.PathPrefix(users.PublicURL).HandlerFunc(userProvider.FederatedUserProfiles)
|
||||
pMux.PathPrefix(httputil.PublicFederationPathPrefix).Handler(base.PublicFederationAPIMux)
|
||||
pMux.PathPrefix(httputil.PublicMediaPathPrefix).Handler(base.PublicMediaAPIMux)
|
||||
|
||||
pHTTP := pQUIC.HTTP()
|
||||
pHTTP := pQUIC.Protocol("matrix").HTTP()
|
||||
pHTTP.Mux().Handle(users.PublicURL, pMux)
|
||||
pHTTP.Mux().Handle(httputil.PublicFederationPathPrefix, pMux)
|
||||
pHTTP.Mux().Handle(httputil.PublicMediaPathPrefix, pMux)
|
||||
|
||||
|
|
@ -270,25 +245,16 @@ func main() {
|
|||
Handler: pMux,
|
||||
}
|
||||
|
||||
go connectToStaticPeer()
|
||||
go func() {
|
||||
pubkey := pRouter.PublicKey()
|
||||
logrus.Info("Listening on ", hex.EncodeToString(pubkey[:]))
|
||||
logrus.Fatal(httpServer.Serve(pQUIC))
|
||||
logrus.Fatal(httpServer.Serve(pQUIC.Protocol("matrix")))
|
||||
}()
|
||||
go func() {
|
||||
httpBindAddr := fmt.Sprintf(":%d", *instancePort)
|
||||
logrus.Info("Listening on ", httpBindAddr)
|
||||
logrus.Fatal(http.ListenAndServe(httpBindAddr, httpRouter))
|
||||
}()
|
||||
go func() {
|
||||
logrus.Info("Sending wake-up message to known nodes")
|
||||
req := &api.PerformBroadcastEDURequest{}
|
||||
res := &api.PerformBroadcastEDUResponse{}
|
||||
if err := fsAPI.PerformBroadcastEDU(context.TODO(), req, res); err != nil {
|
||||
logrus.WithError(err).Error("Failed to send wake-up message to known nodes")
|
||||
}
|
||||
}()
|
||||
|
||||
base.WaitForShutdown()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
// 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.
|
||||
|
|
@ -19,6 +19,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/defaults"
|
||||
"github.com/matrix-org/dendrite/federationapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
|
|
@ -50,9 +51,12 @@ func NewPineconeRoomProvider(
|
|||
}
|
||||
|
||||
func (p *PineconeRoomProvider) Rooms() []gomatrixserverlib.PublicRoom {
|
||||
list := []gomatrixserverlib.ServerName{}
|
||||
list := map[gomatrixserverlib.ServerName]struct{}{}
|
||||
for k := range defaults.DefaultServerNames {
|
||||
list[k] = struct{}{}
|
||||
}
|
||||
for _, k := range p.r.Peers() {
|
||||
list = append(list, gomatrixserverlib.ServerName(k.PublicKey))
|
||||
list[gomatrixserverlib.ServerName(k.PublicKey)] = struct{}{}
|
||||
}
|
||||
return bulkFetchPublicRoomsFromServers(context.Background(), p.fedClient, list)
|
||||
}
|
||||
|
|
@ -61,7 +65,7 @@ func (p *PineconeRoomProvider) Rooms() []gomatrixserverlib.PublicRoom {
|
|||
// Returns a list of public rooms.
|
||||
func bulkFetchPublicRoomsFromServers(
|
||||
ctx context.Context, fedClient *gomatrixserverlib.FederationClient,
|
||||
homeservers []gomatrixserverlib.ServerName,
|
||||
homeservers map[gomatrixserverlib.ServerName]struct{},
|
||||
) (publicRooms []gomatrixserverlib.PublicRoom) {
|
||||
limit := 200
|
||||
// follow pipeline semantics, see https://blog.golang.org/pipelines for more info.
|
||||
|
|
@ -74,7 +78,7 @@ func bulkFetchPublicRoomsFromServers(
|
|||
wg.Add(len(homeservers))
|
||||
// concurrently query for public rooms
|
||||
reqctx, reqcancel := context.WithTimeout(ctx, time.Second*5)
|
||||
for _, hs := range homeservers {
|
||||
for hs := range homeservers {
|
||||
go func(homeserverDomain gomatrixserverlib.ServerName) {
|
||||
defer wg.Done()
|
||||
util.GetLogger(reqctx).WithField("hs", homeserverDomain).Info("Querying HS for public rooms")
|
||||
|
|
|
|||
163
cmd/dendrite-demo-pinecone/users/users.go
Normal file
163
cmd/dendrite-demo-pinecone/users/users.go
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
// 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 users
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
clienthttputil "github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/defaults"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
|
||||
pineconeRouter "github.com/matrix-org/pinecone/router"
|
||||
pineconeSessions "github.com/matrix-org/pinecone/sessions"
|
||||
)
|
||||
|
||||
type PineconeUserProvider struct {
|
||||
r *pineconeRouter.Router
|
||||
s *pineconeSessions.Sessions
|
||||
userAPI userapi.UserProfileAPI
|
||||
fedClient *gomatrixserverlib.FederationClient
|
||||
}
|
||||
|
||||
const PublicURL = "/_matrix/p2p/profiles"
|
||||
|
||||
func NewPineconeUserProvider(
|
||||
r *pineconeRouter.Router,
|
||||
s *pineconeSessions.Sessions,
|
||||
userAPI userapi.UserProfileAPI,
|
||||
fedClient *gomatrixserverlib.FederationClient,
|
||||
) *PineconeUserProvider {
|
||||
p := &PineconeUserProvider{
|
||||
r: r,
|
||||
s: s,
|
||||
userAPI: userAPI,
|
||||
fedClient: fedClient,
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PineconeUserProvider) FederatedUserProfiles(w http.ResponseWriter, r *http.Request) {
|
||||
req := &userapi.QuerySearchProfilesRequest{Limit: 25}
|
||||
res := &userapi.QuerySearchProfilesResponse{}
|
||||
if err := clienthttputil.UnmarshalJSONRequest(r, &req); err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
if err := p.userAPI.QuerySearchProfiles(r.Context(), req, res); err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
j, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
_, _ = w.Write(j)
|
||||
}
|
||||
|
||||
func (p *PineconeUserProvider) QuerySearchProfiles(ctx context.Context, req *userapi.QuerySearchProfilesRequest, res *userapi.QuerySearchProfilesResponse) error {
|
||||
list := map[gomatrixserverlib.ServerName]struct{}{}
|
||||
for k := range defaults.DefaultServerNames {
|
||||
list[k] = struct{}{}
|
||||
}
|
||||
for _, k := range p.r.Peers() {
|
||||
list[gomatrixserverlib.ServerName(k.PublicKey)] = struct{}{}
|
||||
}
|
||||
res.Profiles = bulkFetchUserDirectoriesFromServers(context.Background(), req, p.fedClient, list)
|
||||
return nil
|
||||
}
|
||||
|
||||
// bulkFetchUserDirectoriesFromServers fetches users from the list of homeservers.
|
||||
// Returns a list of user profiles.
|
||||
func bulkFetchUserDirectoriesFromServers(
|
||||
ctx context.Context, req *userapi.QuerySearchProfilesRequest,
|
||||
fedClient *gomatrixserverlib.FederationClient,
|
||||
homeservers map[gomatrixserverlib.ServerName]struct{},
|
||||
) (profiles []authtypes.Profile) {
|
||||
jsonBody, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
limit := 200
|
||||
// follow pipeline semantics, see https://blog.golang.org/pipelines for more info.
|
||||
// goroutines send rooms to this channel
|
||||
profileCh := make(chan authtypes.Profile, int(limit))
|
||||
// signalling channel to tell goroutines to stop sending rooms and quit
|
||||
done := make(chan bool)
|
||||
// signalling to say when we can close the room channel
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(homeservers))
|
||||
// concurrently query for public rooms
|
||||
reqctx, reqcancel := context.WithTimeout(ctx, time.Second*5)
|
||||
for hs := range homeservers {
|
||||
go func(homeserverDomain gomatrixserverlib.ServerName) {
|
||||
defer wg.Done()
|
||||
util.GetLogger(reqctx).WithField("hs", homeserverDomain).Info("Querying HS for users")
|
||||
|
||||
jsonBodyReader := bytes.NewBuffer(jsonBody)
|
||||
httpReq, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("matrix://%s%s", homeserverDomain, PublicURL), jsonBodyReader)
|
||||
if err != nil {
|
||||
util.GetLogger(reqctx).WithError(err).WithField("hs", homeserverDomain).Warn(
|
||||
"bulkFetchUserDirectoriesFromServers: failed to create request",
|
||||
)
|
||||
}
|
||||
res := &userapi.QuerySearchProfilesResponse{}
|
||||
if err = fedClient.DoRequestAndParseResponse(reqctx, httpReq, res); err != nil {
|
||||
util.GetLogger(reqctx).WithError(err).WithField("hs", homeserverDomain).Warn(
|
||||
"bulkFetchUserDirectoriesFromServers: failed to query hs",
|
||||
)
|
||||
return
|
||||
}
|
||||
for _, profile := range res.Profiles {
|
||||
profile.ServerName = string(homeserverDomain)
|
||||
// atomically send a room or stop
|
||||
select {
|
||||
case profileCh <- profile:
|
||||
case <-done:
|
||||
case <-reqctx.Done():
|
||||
util.GetLogger(reqctx).WithError(err).WithField("hs", homeserverDomain).Info("Interrupted whilst sending profiles")
|
||||
return
|
||||
}
|
||||
}
|
||||
}(hs)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
default:
|
||||
wg.Wait()
|
||||
}
|
||||
reqcancel()
|
||||
close(done)
|
||||
close(profileCh)
|
||||
|
||||
for profile := range profileCh {
|
||||
profiles = append(profiles, profile)
|
||||
}
|
||||
|
||||
return profiles
|
||||
}
|
||||
|
|
@ -32,8 +32,6 @@ import (
|
|||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing"
|
||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/yggconn"
|
||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/yggrooms"
|
||||
"github.com/matrix-org/dendrite/eduserver"
|
||||
"github.com/matrix-org/dendrite/eduserver/cache"
|
||||
"github.com/matrix-org/dendrite/federationapi"
|
||||
"github.com/matrix-org/dendrite/federationapi/api"
|
||||
"github.com/matrix-org/dendrite/internal"
|
||||
|
|
@ -120,10 +118,6 @@ func main() {
|
|||
userAPI := userapi.NewInternalAPI(base, accountDB, &cfg.UserAPI, nil, keyAPI, rsAPI, base.PushGatewayHTTPClient())
|
||||
keyAPI.SetUserAPI(userAPI)
|
||||
|
||||
eduInputAPI := eduserver.NewInternalAPI(
|
||||
base, cache.New(), userAPI,
|
||||
)
|
||||
|
||||
asAPI := appservice.NewInternalAPI(base, userAPI, rsAPI)
|
||||
rsAPI.SetAppserviceAPI(asAPI)
|
||||
fsAPI := federationapi.NewInternalAPI(
|
||||
|
|
@ -139,12 +133,11 @@ func main() {
|
|||
FedClient: federation,
|
||||
KeyRing: keyRing,
|
||||
|
||||
AppserviceAPI: asAPI,
|
||||
EDUInternalAPI: eduInputAPI,
|
||||
FederationAPI: fsAPI,
|
||||
RoomserverAPI: rsAPI,
|
||||
UserAPI: userAPI,
|
||||
KeyAPI: keyAPI,
|
||||
AppserviceAPI: asAPI,
|
||||
FederationAPI: fsAPI,
|
||||
RoomserverAPI: rsAPI,
|
||||
UserAPI: userAPI,
|
||||
KeyAPI: keyAPI,
|
||||
ExtPublicRoomsProvider: yggrooms.NewYggdrasilRoomProvider(
|
||||
ygg, fsAPI, federation,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/matrix-org/dendrite/appservice"
|
||||
"github.com/matrix-org/dendrite/eduserver"
|
||||
"github.com/matrix-org/dendrite/eduserver/cache"
|
||||
"github.com/matrix-org/dendrite/federationapi"
|
||||
"github.com/matrix-org/dendrite/keyserver"
|
||||
"github.com/matrix-org/dendrite/roomserver"
|
||||
|
|
@ -61,7 +59,6 @@ func main() {
|
|||
// itself.
|
||||
cfg.AppServiceAPI.InternalAPI.Connect = httpAPIAddr
|
||||
cfg.ClientAPI.InternalAPI.Connect = httpAPIAddr
|
||||
cfg.EDUServer.InternalAPI.Connect = httpAPIAddr
|
||||
cfg.FederationAPI.InternalAPI.Connect = httpAPIAddr
|
||||
cfg.KeyServer.InternalAPI.Connect = httpAPIAddr
|
||||
cfg.MediaAPI.InternalAPI.Connect = httpAPIAddr
|
||||
|
|
@ -136,14 +133,6 @@ func main() {
|
|||
rsImpl.SetUserAPI(userAPI)
|
||||
keyImpl.SetUserAPI(userAPI)
|
||||
|
||||
eduInputAPI := eduserver.NewInternalAPI(
|
||||
base, cache.New(), userAPI,
|
||||
)
|
||||
if base.UseHTTPAPIs {
|
||||
eduserver.AddInternalRoutes(base.InternalAPIMux, eduInputAPI)
|
||||
eduInputAPI = base.EDUServerClient()
|
||||
}
|
||||
|
||||
monolith := setup.Monolith{
|
||||
Config: base.Cfg,
|
||||
AccountDB: accountDB,
|
||||
|
|
@ -151,12 +140,10 @@ func main() {
|
|||
FedClient: federation,
|
||||
KeyRing: keyRing,
|
||||
|
||||
AppserviceAPI: asAPI,
|
||||
EDUInternalAPI: eduInputAPI,
|
||||
FederationAPI: fsAPI,
|
||||
RoomserverAPI: rsAPI,
|
||||
UserAPI: userAPI,
|
||||
KeyAPI: keyAPI,
|
||||
AppserviceAPI: asAPI, FederationAPI: fsAPI,
|
||||
RoomserverAPI: rsAPI,
|
||||
UserAPI: userAPI,
|
||||
KeyAPI: keyAPI,
|
||||
}
|
||||
monolith.AddAllPublicRoutes(
|
||||
base.ProcessContext,
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ func main() {
|
|||
components := map[string]entrypoint{
|
||||
"appservice": personalities.Appservice,
|
||||
"clientapi": personalities.ClientAPI,
|
||||
"eduserver": personalities.EDUServer,
|
||||
"federationapi": personalities.FederationAPI,
|
||||
"keyserver": personalities.KeyServer,
|
||||
"mediaapi": personalities.MediaAPI,
|
||||
|
|
|
|||
|
|
@ -27,13 +27,12 @@ func ClientAPI(base *basepkg.BaseDendrite, cfg *config.Dendrite) {
|
|||
asQuery := base.AppserviceHTTPClient()
|
||||
rsAPI := base.RoomserverHTTPClient()
|
||||
fsAPI := base.FederationAPIHTTPClient()
|
||||
eduInputAPI := base.EDUServerClient()
|
||||
userAPI := base.UserAPIClient()
|
||||
keyAPI := base.KeyServerHTTPClient()
|
||||
|
||||
clientapi.AddPublicRoutes(
|
||||
base.ProcessContext, base.PublicClientAPIMux, base.SynapseAdminMux, &base.Cfg.ClientAPI,
|
||||
federation, rsAPI, eduInputAPI, asQuery, transactions.New(), fsAPI, userAPI,
|
||||
federation, rsAPI, asQuery, transactions.New(), fsAPI, userAPI, userAPI,
|
||||
keyAPI, nil, &cfg.MSCs,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
// Copyright 2020 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 personalities
|
||||
|
||||
import (
|
||||
"github.com/matrix-org/dendrite/eduserver"
|
||||
"github.com/matrix-org/dendrite/eduserver/cache"
|
||||
basepkg "github.com/matrix-org/dendrite/setup/base"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
)
|
||||
|
||||
func EDUServer(base *basepkg.BaseDendrite, cfg *config.Dendrite) {
|
||||
intAPI := eduserver.NewInternalAPI(base, cache.New(), base.UserAPIClient())
|
||||
eduserver.AddInternalRoutes(base.InternalAPIMux, intAPI)
|
||||
|
||||
base.SetupAndServeHTTP(
|
||||
base.Cfg.EDUServer.InternalAPI.Listen, // internal listener
|
||||
basepkg.NoListener, // external listener
|
||||
nil, nil,
|
||||
)
|
||||
}
|
||||
|
|
@ -29,9 +29,9 @@ func FederationAPI(base *basepkg.BaseDendrite, cfg *config.Dendrite) {
|
|||
keyRing := fsAPI.KeyRing()
|
||||
|
||||
federationapi.AddPublicRoutes(
|
||||
base.PublicFederationAPIMux, base.PublicKeyAPIMux, base.PublicWellKnownAPIMux,
|
||||
base.ProcessContext, base.PublicFederationAPIMux, base.PublicKeyAPIMux, base.PublicWellKnownAPIMux,
|
||||
&base.Cfg.FederationAPI, userAPI, federation, keyRing,
|
||||
rsAPI, fsAPI, base.EDUServerClient(), keyAPI,
|
||||
rsAPI, fsAPI, keyAPI,
|
||||
&base.Cfg.MSCs, nil,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,18 +21,13 @@ import (
|
|||
"crypto/ed25519"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"syscall/js"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/matrix-org/dendrite/appservice"
|
||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/conn"
|
||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/rooms"
|
||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing"
|
||||
"github.com/matrix-org/dendrite/eduserver"
|
||||
"github.com/matrix-org/dendrite/eduserver/cache"
|
||||
"github.com/matrix-org/dendrite/federationapi"
|
||||
"github.com/matrix-org/dendrite/internal/httputil"
|
||||
"github.com/matrix-org/dendrite/keyserver"
|
||||
|
|
@ -48,6 +43,7 @@ import (
|
|||
|
||||
_ "github.com/matrix-org/go-sqlite3-js"
|
||||
|
||||
pineconeConnections "github.com/matrix-org/pinecone/connections"
|
||||
pineconeRouter "github.com/matrix-org/pinecone/router"
|
||||
pineconeSessions "github.com/matrix-org/pinecone/sessions"
|
||||
)
|
||||
|
|
@ -156,9 +152,10 @@ func startup() {
|
|||
sk := generateKey()
|
||||
pk := sk.Public().(ed25519.PublicKey)
|
||||
|
||||
logger := log.New(os.Stdout, "", 0)
|
||||
pRouter := pineconeRouter.NewRouter(logger, sk, false)
|
||||
pSessions := pineconeSessions.NewSessions(logger, pRouter)
|
||||
pRouter := pineconeRouter.NewRouter(logrus.WithField("pinecone", "router"), sk, false)
|
||||
pSessions := pineconeSessions.NewSessions(logrus.WithField("pinecone", "sessions"), pRouter, []string{"matrix"})
|
||||
pManager := pineconeConnections.NewConnectionManager(pRouter)
|
||||
pManager.AddPeer("wss://pinecone.matrix.org/public")
|
||||
|
||||
cfg := &config.Dendrite{}
|
||||
cfg.Defaults(true)
|
||||
|
|
@ -193,7 +190,6 @@ func startup() {
|
|||
userAPI := userapi.NewInternalAPI(base, accountDB, &cfg.UserAPI, nil, keyAPI, rsAPI, base.PushGatewayHTTPClient())
|
||||
keyAPI.SetUserAPI(userAPI)
|
||||
|
||||
eduInputAPI := eduserver.NewInternalAPI(base, cache.New(), userAPI)
|
||||
asQuery := appservice.NewInternalAPI(
|
||||
base, userAPI, rsAPI,
|
||||
)
|
||||
|
|
@ -208,12 +204,11 @@ func startup() {
|
|||
FedClient: federation,
|
||||
KeyRing: keyRing,
|
||||
|
||||
AppserviceAPI: asQuery,
|
||||
EDUInternalAPI: eduInputAPI,
|
||||
FederationAPI: fedSenderAPI,
|
||||
RoomserverAPI: rsAPI,
|
||||
UserAPI: userAPI,
|
||||
KeyAPI: keyAPI,
|
||||
AppserviceAPI: asQuery,
|
||||
FederationAPI: fedSenderAPI,
|
||||
RoomserverAPI: rsAPI,
|
||||
UserAPI: userAPI,
|
||||
KeyAPI: keyAPI,
|
||||
//ServerKeyAPI: serverKeyAPI,
|
||||
ExtPublicRoomsProvider: rooms.NewPineconeRoomProvider(pRouter, pSessions, fedSenderAPI, federation),
|
||||
}
|
||||
|
|
@ -232,7 +227,7 @@ func startup() {
|
|||
httpRouter.PathPrefix(httputil.PublicClientPathPrefix).Handler(base.PublicClientAPIMux)
|
||||
httpRouter.PathPrefix(httputil.PublicMediaPathPrefix).Handler(base.PublicMediaAPIMux)
|
||||
|
||||
p2pRouter := pSessions.HTTP().Mux()
|
||||
p2pRouter := pSessions.Protocol("matrix").HTTP().Mux()
|
||||
p2pRouter.Handle(httputil.PublicFederationPathPrefix, base.PublicFederationAPIMux)
|
||||
p2pRouter.Handle(httputil.PublicMediaPathPrefix, base.PublicMediaAPIMux)
|
||||
|
||||
|
|
@ -244,20 +239,4 @@ func startup() {
|
|||
}
|
||||
s.ListenAndServe("fetch")
|
||||
}()
|
||||
|
||||
// Connect to the static peer
|
||||
go func() {
|
||||
for {
|
||||
if pRouter.PeerCount(pineconeRouter.PeerTypeRemote) == 0 {
|
||||
if err := conn.ConnectToPeer(pRouter, publicPeer); err != nil {
|
||||
logrus.WithError(err).Error("Failed to connect to static peer")
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-base.ProcessContext.Context().Done():
|
||||
return
|
||||
case <-time.After(time.Second * 5):
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,6 @@ import (
|
|||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/matrix-org/dendrite/appservice"
|
||||
"github.com/matrix-org/dendrite/eduserver"
|
||||
"github.com/matrix-org/dendrite/eduserver/cache"
|
||||
"github.com/matrix-org/dendrite/federationapi"
|
||||
"github.com/matrix-org/dendrite/internal/httputil"
|
||||
"github.com/matrix-org/dendrite/keyserver"
|
||||
|
|
@ -203,7 +201,6 @@ func main() {
|
|||
}
|
||||
|
||||
rsAPI := roomserver.NewInternalAPI(base)
|
||||
eduInputAPI := eduserver.NewInternalAPI(base, cache.New(), userAPI)
|
||||
asQuery := appservice.NewInternalAPI(
|
||||
base, userAPI, rsAPI,
|
||||
)
|
||||
|
|
@ -222,7 +219,6 @@ func main() {
|
|||
KeyRing: &keyRing,
|
||||
|
||||
AppserviceAPI: asQuery,
|
||||
EDUInternalAPI: eduInputAPI,
|
||||
FederationSenderAPI: fedSenderAPI,
|
||||
RoomserverAPI: rsAPI,
|
||||
UserAPI: userAPI,
|
||||
|
|
|
|||
|
|
@ -1,138 +0,0 @@
|
|||
// Copyright 2017 Vector Creations Ltd
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const usage = `Usage: %s
|
||||
|
||||
Create a single endpoint URL which remote matrix servers can be pointed at.
|
||||
|
||||
The server-server API in Dendrite is split across multiple processes
|
||||
which listen on multiple ports. You cannot point a Matrix server at
|
||||
any of those ports, as there will be unimplemented functionality.
|
||||
In addition, all server-server API processes start with the additional
|
||||
path prefix '/api', which Matrix servers will be unaware of.
|
||||
|
||||
This tool will proxy requests for all server-server URLs and forward
|
||||
them to their respective process. It will also add the '/api' path
|
||||
prefix to incoming requests.
|
||||
|
||||
THIS TOOL IS FOR TESTING AND NOT INTENDED FOR PRODUCTION USE.
|
||||
|
||||
Arguments:
|
||||
|
||||
`
|
||||
|
||||
var (
|
||||
federationAPIURL = flag.String("federation-api-url", "", "The base URL of the listening 'dendrite-federation-api-server' process. E.g. 'http://localhost:4200'")
|
||||
mediaAPIURL = flag.String("media-api-server-url", "", "The base URL of the listening 'dendrite-media-api-server' process. E.g. 'http://localhost:7779'")
|
||||
bindAddress = flag.String("bind-address", ":8448", "The listening port for the proxy.")
|
||||
certFile = flag.String("tls-cert", "server.crt", "The PEM formatted X509 certificate to use for TLS")
|
||||
keyFile = flag.String("tls-key", "server.key", "The PEM private key to use for TLS")
|
||||
)
|
||||
|
||||
func makeProxy(targetURL string) (*httputil.ReverseProxy, error) {
|
||||
if !strings.HasSuffix(targetURL, "/") {
|
||||
targetURL += "/"
|
||||
}
|
||||
// Check that we can parse the URL.
|
||||
_, err := url.Parse(targetURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &httputil.ReverseProxy{
|
||||
Director: func(req *http.Request) {
|
||||
// URL.Path() removes the % escaping from the path.
|
||||
// The % encoding will be added back when the url is encoded
|
||||
// when the request is forwarded.
|
||||
// This means that we will lose any unessecary escaping from the URL.
|
||||
// Pratically this means that any distinction between '%2F' and '/'
|
||||
// in the URL will be lost by the time it reaches the target.
|
||||
path := req.URL.Path
|
||||
log.WithFields(log.Fields{
|
||||
"path": path,
|
||||
"url": targetURL,
|
||||
"method": req.Method,
|
||||
}).Print("proxying request")
|
||||
newURL, err := url.Parse(targetURL + path)
|
||||
if err != nil {
|
||||
// We already checked that we can parse the URL
|
||||
// So this shouldn't ever get hit.
|
||||
panic(err)
|
||||
}
|
||||
// Copy the query parameters from the request.
|
||||
newURL.RawQuery = req.URL.RawQuery
|
||||
req.URL = newURL
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, usage, os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *federationAPIURL == "" {
|
||||
flag.Usage()
|
||||
fmt.Fprintln(os.Stderr, "no --federation-api-url specified.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if *mediaAPIURL == "" {
|
||||
flag.Usage()
|
||||
fmt.Fprintln(os.Stderr, "no --media-api-server-url specified.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
federationProxy, err := makeProxy(*federationAPIURL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
mediaProxy, err := makeProxy(*mediaAPIURL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
http.Handle("/_matrix/media/v1/", mediaProxy)
|
||||
http.Handle("/", federationProxy)
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: *bindAddress,
|
||||
ReadTimeout: 1 * time.Minute, // how long we wait for the client to send the entire request (after connection accept)
|
||||
WriteTimeout: 5 * time.Minute, // how long the proxy has to write the full response
|
||||
}
|
||||
|
||||
fmt.Println("Proxying requests to:")
|
||||
fmt.Println(" /_matrix/media/v1 => ", *mediaAPIURL+"/api/_matrix/media/v1")
|
||||
fmt.Println(" /* => ", *federationAPIURL+"/api/*")
|
||||
fmt.Println("Listening on ", *bindAddress)
|
||||
panic(srv.ListenAndServeTLS(*certFile, *keyFile))
|
||||
}
|
||||
|
|
@ -91,6 +91,10 @@ func main() {
|
|||
cfg.UserAPI.BCryptCost = bcrypt.MinCost
|
||||
cfg.Global.JetStream.InMemory = true
|
||||
cfg.ClientAPI.RegistrationSharedSecret = "complement"
|
||||
cfg.Global.Presence = config.PresenceOptions{
|
||||
EnableInbound: true,
|
||||
EnableOutbound: true,
|
||||
}
|
||||
}
|
||||
|
||||
j, err := yaml.Marshal(cfg)
|
||||
|
|
|
|||
|
|
@ -68,6 +68,13 @@ global:
|
|||
# to other servers and the federation API will not be exposed.
|
||||
disable_federation: false
|
||||
|
||||
# Configures the handling of presence events.
|
||||
presence:
|
||||
# Whether inbound presence events are allowed, e.g. receiving presence events from other servers
|
||||
enable_inbound: false
|
||||
# Whether outbound presence events are allowed, e.g. sending presence events to other servers
|
||||
enable_outbound: false
|
||||
|
||||
# Server notices allows server admins to send messages to all users.
|
||||
server_notices:
|
||||
enabled: false
|
||||
|
|
@ -187,12 +194,6 @@ client_api:
|
|||
threshold: 5
|
||||
cooloff_ms: 500
|
||||
|
||||
# Configuration for the EDU server.
|
||||
edu_server:
|
||||
internal_api:
|
||||
listen: http://localhost:7778 # Only used in polylith deployments
|
||||
connect: http://localhost:7778 # Only used in polylith deployments
|
||||
|
||||
# Configuration for the Federation API.
|
||||
federation_api:
|
||||
internal_api:
|
||||
|
|
@ -206,12 +207,6 @@ federation_api:
|
|||
max_idle_conns: 2
|
||||
conn_max_lifetime: -1
|
||||
|
||||
# List of paths to X.509 certificates to be used by the external federation listeners.
|
||||
# These certificates will be used to calculate the TLS fingerprints and other servers
|
||||
# will expect the certificate to match these fingerprints. Certificates must be in PEM
|
||||
# format.
|
||||
federation_certificates: []
|
||||
|
||||
# How many times we will try to resend a failed transaction to a specific server. The
|
||||
# backoff is 2**x seconds, so 1 = 2 seconds, 2 = 4 seconds, 3 = 8 seconds etc.
|
||||
send_max_retries: 16
|
||||
|
|
|
|||
|
|
@ -94,4 +94,4 @@ For more general questions please use
|
|||
|
||||
We ask that everyone who contributes to the project signs off their
|
||||
contributions, in accordance with the
|
||||
[DCO](https://github.com/matrix-org/matrix-doc/blob/main/CONTRIBUTING.rst#sign-off).
|
||||
[DCO](https://github.com/matrix-org/matrix-spec/blob/main/CONTRIBUTING.rst#sign-off).
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ If you want to run a polylith deployment, you also need:
|
|||
|
||||
* A standalone [NATS Server](https://github.com/nats-io/nats-server) deployment with JetStream enabled
|
||||
|
||||
If you want to build it on Windows, you need `gcc` in the path:
|
||||
|
||||
* [MinGW-w64](https://www.mingw-w64.org/)
|
||||
|
||||
## Building Dendrite
|
||||
|
||||
Start by cloning the code:
|
||||
|
|
@ -45,9 +49,15 @@ cd dendrite
|
|||
|
||||
Then build it:
|
||||
|
||||
```bash
|
||||
./build.sh
|
||||
```
|
||||
* Linux or UNIX-like systems:
|
||||
```bash
|
||||
./build.sh
|
||||
```
|
||||
|
||||
* Windows:
|
||||
```dos
|
||||
build.cmd
|
||||
```
|
||||
|
||||
## Install NATS Server
|
||||
|
||||
|
|
@ -263,14 +273,6 @@ This manages end-to-end encryption keys for users.
|
|||
./bin/dendrite-polylith-multi --config=dendrite.yaml keyserver
|
||||
```
|
||||
|
||||
#### EDU server
|
||||
|
||||
This manages processing EDUs such as typing, send-to-device events and presence. Clients do not talk to
|
||||
|
||||
```bash
|
||||
./bin/dendrite-polylith-multi --config=dendrite.yaml eduserver
|
||||
```
|
||||
|
||||
#### User server
|
||||
|
||||
This manages user accounts, device access tokens and user account data,
|
||||
|
|
|
|||
|
|
@ -1,103 +0,0 @@
|
|||
// Copyright 2017 Vector Creations Ltd
|
||||
// Copyright 2017-2018 New Vector Ltd
|
||||
// Copyright 2019-2020 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 api provides the types that are used to communicate with the typing server.
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
)
|
||||
|
||||
// InputTypingEvent is an event for notifying the typing server about typing updates.
|
||||
type InputTypingEvent struct {
|
||||
// UserID of the user to update typing status.
|
||||
UserID string `json:"user_id"`
|
||||
// RoomID of the room the user is typing (or has stopped).
|
||||
RoomID string `json:"room_id"`
|
||||
// Typing is true if the user is typing, false if they have stopped.
|
||||
Typing bool `json:"typing"`
|
||||
// Timeout is the interval in milliseconds for which the user should be marked as typing.
|
||||
TimeoutMS int64 `json:"timeout"`
|
||||
// OriginServerTS when the server received the update.
|
||||
OriginServerTS gomatrixserverlib.Timestamp `json:"origin_server_ts"`
|
||||
}
|
||||
|
||||
type InputSendToDeviceEvent struct {
|
||||
UserID string `json:"user_id"`
|
||||
DeviceID string `json:"device_id"`
|
||||
gomatrixserverlib.SendToDeviceEvent
|
||||
}
|
||||
|
||||
// InputTypingEventRequest is a request to EDUServerInputAPI
|
||||
type InputTypingEventRequest struct {
|
||||
InputTypingEvent InputTypingEvent `json:"input_typing_event"`
|
||||
}
|
||||
|
||||
// InputTypingEventResponse is a response to InputTypingEvents
|
||||
type InputTypingEventResponse struct{}
|
||||
|
||||
// InputSendToDeviceEventRequest is a request to EDUServerInputAPI
|
||||
type InputSendToDeviceEventRequest struct {
|
||||
InputSendToDeviceEvent InputSendToDeviceEvent `json:"input_send_to_device_event"`
|
||||
}
|
||||
|
||||
// InputSendToDeviceEventResponse is a response to InputSendToDeviceEventRequest
|
||||
type InputSendToDeviceEventResponse struct{}
|
||||
|
||||
type InputReceiptEvent struct {
|
||||
UserID string `json:"user_id"`
|
||||
RoomID string `json:"room_id"`
|
||||
EventID string `json:"event_id"`
|
||||
Type string `json:"type"`
|
||||
Timestamp gomatrixserverlib.Timestamp `json:"timestamp"`
|
||||
}
|
||||
|
||||
// InputReceiptEventRequest is a request to EDUServerInputAPI
|
||||
type InputReceiptEventRequest struct {
|
||||
InputReceiptEvent InputReceiptEvent `json:"input_receipt_event"`
|
||||
}
|
||||
|
||||
// InputReceiptEventResponse is a response to InputReceiptEventRequest
|
||||
type InputReceiptEventResponse struct{}
|
||||
|
||||
type InputCrossSigningKeyUpdateRequest struct {
|
||||
CrossSigningKeyUpdate `json:"signing_keys"`
|
||||
}
|
||||
|
||||
type InputCrossSigningKeyUpdateResponse struct{}
|
||||
|
||||
// EDUServerInputAPI is used to write events to the typing server.
|
||||
type EDUServerInputAPI interface {
|
||||
InputTypingEvent(
|
||||
ctx context.Context,
|
||||
request *InputTypingEventRequest,
|
||||
response *InputTypingEventResponse,
|
||||
) error
|
||||
|
||||
InputSendToDeviceEvent(
|
||||
ctx context.Context,
|
||||
request *InputSendToDeviceEventRequest,
|
||||
response *InputSendToDeviceEventResponse,
|
||||
) error
|
||||
|
||||
InputReceiptEvent(
|
||||
ctx context.Context,
|
||||
request *InputReceiptEventRequest,
|
||||
response *InputReceiptEventResponse,
|
||||
) error
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
// Copyright 2017 Vector Creations Ltd
|
||||
// Copyright 2017-2018 New Vector Ltd
|
||||
// Copyright 2019-2020 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 api
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
)
|
||||
|
||||
// OutputTypingEvent is an entry in typing server output kafka log.
|
||||
// This contains the event with extra fields used to create 'm.typing' event
|
||||
// in clientapi & federation.
|
||||
type OutputTypingEvent struct {
|
||||
// The Event for the typing edu event.
|
||||
Event TypingEvent `json:"event"`
|
||||
// ExpireTime is the interval after which the user should no longer be
|
||||
// considered typing. Only available if Event.Typing is true.
|
||||
ExpireTime *time.Time
|
||||
}
|
||||
|
||||
// OutputSendToDeviceEvent is an entry in the send-to-device output kafka log.
|
||||
// This contains the full event content, along with the user ID and device ID
|
||||
// to which it is destined.
|
||||
type OutputSendToDeviceEvent struct {
|
||||
UserID string `json:"user_id"`
|
||||
DeviceID string `json:"device_id"`
|
||||
gomatrixserverlib.SendToDeviceEvent
|
||||
}
|
||||
|
||||
// OutputReceiptEvent is an entry in the receipt output kafka log
|
||||
type OutputReceiptEvent struct {
|
||||
UserID string `json:"user_id"`
|
||||
RoomID string `json:"room_id"`
|
||||
EventID string `json:"event_id"`
|
||||
Type string `json:"type"`
|
||||
Timestamp gomatrixserverlib.Timestamp `json:"timestamp"`
|
||||
}
|
||||
|
||||
// OutputCrossSigningKeyUpdate is an entry in the signing key update output kafka log
|
||||
type OutputCrossSigningKeyUpdate struct {
|
||||
CrossSigningKeyUpdate `json:"signing_keys"`
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
// Copyright 2021 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 api
|
||||
|
||||
import "github.com/matrix-org/gomatrixserverlib"
|
||||
|
||||
const (
|
||||
MSigningKeyUpdate = "m.signing_key_update"
|
||||
)
|
||||
|
||||
type TypingEvent struct {
|
||||
Type string `json:"type"`
|
||||
RoomID string `json:"room_id"`
|
||||
UserID string `json:"user_id"`
|
||||
Typing bool `json:"typing"`
|
||||
}
|
||||
|
||||
type ReceiptEvent struct {
|
||||
UserID string `json:"user_id"`
|
||||
RoomID string `json:"room_id"`
|
||||
EventID string `json:"event_id"`
|
||||
Type string `json:"type"`
|
||||
Timestamp gomatrixserverlib.Timestamp `json:"timestamp"`
|
||||
}
|
||||
|
||||
type FederationReceiptMRead struct {
|
||||
User map[string]FederationReceiptData `json:"m.read"`
|
||||
}
|
||||
|
||||
type FederationReceiptData struct {
|
||||
Data ReceiptTS `json:"data"`
|
||||
EventIDs []string `json:"event_ids"`
|
||||
}
|
||||
|
||||
type ReceiptMRead struct {
|
||||
User map[string]ReceiptTS `json:"m.read"`
|
||||
}
|
||||
|
||||
type ReceiptTS struct {
|
||||
TS gomatrixserverlib.Timestamp `json:"ts"`
|
||||
}
|
||||
|
||||
type CrossSigningKeyUpdate struct {
|
||||
MasterKey *gomatrixserverlib.CrossSigningKey `json:"master_key,omitempty"`
|
||||
SelfSigningKey *gomatrixserverlib.CrossSigningKey `json:"self_signing_key,omitempty"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
// Copyright 2020 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 api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
)
|
||||
|
||||
// SendTyping sends a typing event to EDU server
|
||||
func SendTyping(
|
||||
ctx context.Context, eduAPI EDUServerInputAPI, userID, roomID string,
|
||||
typing bool, timeoutMS int64,
|
||||
) error {
|
||||
requestData := InputTypingEvent{
|
||||
UserID: userID,
|
||||
RoomID: roomID,
|
||||
Typing: typing,
|
||||
TimeoutMS: timeoutMS,
|
||||
OriginServerTS: gomatrixserverlib.AsTimestamp(time.Now()),
|
||||
}
|
||||
|
||||
var response InputTypingEventResponse
|
||||
err := eduAPI.InputTypingEvent(
|
||||
ctx, &InputTypingEventRequest{InputTypingEvent: requestData}, &response,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// SendToDevice sends a typing event to EDU server
|
||||
func SendToDevice(
|
||||
ctx context.Context, eduAPI EDUServerInputAPI, sender, userID, deviceID, eventType string,
|
||||
message interface{},
|
||||
) error {
|
||||
js, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
requestData := InputSendToDeviceEvent{
|
||||
UserID: userID,
|
||||
DeviceID: deviceID,
|
||||
SendToDeviceEvent: gomatrixserverlib.SendToDeviceEvent{
|
||||
Sender: sender,
|
||||
Type: eventType,
|
||||
Content: js,
|
||||
},
|
||||
}
|
||||
request := InputSendToDeviceEventRequest{
|
||||
InputSendToDeviceEvent: requestData,
|
||||
}
|
||||
response := InputSendToDeviceEventResponse{}
|
||||
return eduAPI.InputSendToDeviceEvent(ctx, &request, &response)
|
||||
}
|
||||
|
||||
// SendReceipt sends a receipt event to EDU Server
|
||||
func SendReceipt(
|
||||
ctx context.Context,
|
||||
eduAPI EDUServerInputAPI, userID, roomID, eventID, receiptType string,
|
||||
timestamp gomatrixserverlib.Timestamp,
|
||||
) error {
|
||||
request := InputReceiptEventRequest{
|
||||
InputReceiptEvent: InputReceiptEvent{
|
||||
UserID: userID,
|
||||
RoomID: roomID,
|
||||
EventID: eventID,
|
||||
Type: receiptType,
|
||||
Timestamp: timestamp,
|
||||
},
|
||||
}
|
||||
response := InputReceiptEventResponse{}
|
||||
return eduAPI.InputReceiptEvent(ctx, &request, &response)
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
// Copyright 2017 Vector Creations Ltd
|
||||
// Copyright 2017-2018 New Vector Ltd
|
||||
// Copyright 2019-2020 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 eduserver
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/matrix-org/dendrite/eduserver/api"
|
||||
"github.com/matrix-org/dendrite/eduserver/cache"
|
||||
"github.com/matrix-org/dendrite/eduserver/input"
|
||||
"github.com/matrix-org/dendrite/eduserver/inthttp"
|
||||
"github.com/matrix-org/dendrite/setup/base"
|
||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
)
|
||||
|
||||
// AddInternalRoutes registers HTTP handlers for the internal API. Invokes functions
|
||||
// on the given input API.
|
||||
func AddInternalRoutes(internalMux *mux.Router, inputAPI api.EDUServerInputAPI) {
|
||||
inthttp.AddRoutes(inputAPI, internalMux)
|
||||
}
|
||||
|
||||
// NewInternalAPI returns a concerete implementation of the internal API. Callers
|
||||
// can call functions directly on the returned API or via an HTTP interface using AddInternalRoutes.
|
||||
func NewInternalAPI(
|
||||
base *base.BaseDendrite,
|
||||
eduCache *cache.EDUCache,
|
||||
userAPI userapi.UserInternalAPI,
|
||||
) api.EDUServerInputAPI {
|
||||
cfg := &base.Cfg.EDUServer
|
||||
|
||||
js, _ := jetstream.Prepare(base.ProcessContext, &cfg.Matrix.JetStream)
|
||||
|
||||
return &input.EDUServerInputAPI{
|
||||
Cache: eduCache,
|
||||
UserAPI: userAPI,
|
||||
JetStream: js,
|
||||
OutputTypingEventTopic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputTypingEvent),
|
||||
OutputSendToDeviceEventTopic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputSendToDeviceEvent),
|
||||
OutputReceiptEventTopic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputReceiptEvent),
|
||||
ServerName: cfg.Matrix.ServerName,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
// Copyright 2017 Vector Creations Ltd
|
||||
// Copyright 2017-2018 New Vector Ltd
|
||||
// Copyright 2019-2020 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 input
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/eduserver/api"
|
||||
"github.com/matrix-org/dendrite/eduserver/cache"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// EDUServerInputAPI implements api.EDUServerInputAPI
|
||||
type EDUServerInputAPI struct {
|
||||
// Cache to store the current typing members in each room.
|
||||
Cache *cache.EDUCache
|
||||
// The kafka topic to output new typing events to.
|
||||
OutputTypingEventTopic string
|
||||
// The kafka topic to output new send to device events to.
|
||||
OutputSendToDeviceEventTopic string
|
||||
// The kafka topic to output new receipt events to
|
||||
OutputReceiptEventTopic string
|
||||
// kafka producer
|
||||
JetStream nats.JetStreamContext
|
||||
// Internal user query API
|
||||
UserAPI userapi.UserInternalAPI
|
||||
// our server name
|
||||
ServerName gomatrixserverlib.ServerName
|
||||
}
|
||||
|
||||
// InputTypingEvent implements api.EDUServerInputAPI
|
||||
func (t *EDUServerInputAPI) InputTypingEvent(
|
||||
ctx context.Context,
|
||||
request *api.InputTypingEventRequest,
|
||||
response *api.InputTypingEventResponse,
|
||||
) error {
|
||||
ite := &request.InputTypingEvent
|
||||
if ite.Typing {
|
||||
// user is typing, update our current state of users typing.
|
||||
expireTime := ite.OriginServerTS.Time().Add(
|
||||
time.Duration(ite.TimeoutMS) * time.Millisecond,
|
||||
)
|
||||
t.Cache.AddTypingUser(ite.UserID, ite.RoomID, &expireTime)
|
||||
} else {
|
||||
t.Cache.RemoveUser(ite.UserID, ite.RoomID)
|
||||
}
|
||||
|
||||
return t.sendTypingEvent(ite)
|
||||
}
|
||||
|
||||
// InputTypingEvent implements api.EDUServerInputAPI
|
||||
func (t *EDUServerInputAPI) InputSendToDeviceEvent(
|
||||
ctx context.Context,
|
||||
request *api.InputSendToDeviceEventRequest,
|
||||
response *api.InputSendToDeviceEventResponse,
|
||||
) error {
|
||||
ise := &request.InputSendToDeviceEvent
|
||||
return t.sendToDeviceEvent(ise)
|
||||
}
|
||||
|
||||
func (t *EDUServerInputAPI) sendTypingEvent(ite *api.InputTypingEvent) error {
|
||||
ev := &api.TypingEvent{
|
||||
Type: gomatrixserverlib.MTyping,
|
||||
RoomID: ite.RoomID,
|
||||
UserID: ite.UserID,
|
||||
Typing: ite.Typing,
|
||||
}
|
||||
ote := &api.OutputTypingEvent{
|
||||
Event: *ev,
|
||||
}
|
||||
|
||||
if ev.Typing {
|
||||
expireTime := ite.OriginServerTS.Time().Add(
|
||||
time.Duration(ite.TimeoutMS) * time.Millisecond,
|
||||
)
|
||||
ote.ExpireTime = &expireTime
|
||||
}
|
||||
|
||||
eventJSON, err := json.Marshal(ote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"room_id": ite.RoomID,
|
||||
"user_id": ite.UserID,
|
||||
"typing": ite.Typing,
|
||||
}).Tracef("Producing to topic '%s'", t.OutputTypingEventTopic)
|
||||
|
||||
_, err = t.JetStream.PublishMsg(&nats.Msg{
|
||||
Subject: t.OutputTypingEventTopic,
|
||||
Header: nats.Header{},
|
||||
Data: eventJSON,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *EDUServerInputAPI) sendToDeviceEvent(ise *api.InputSendToDeviceEvent) error {
|
||||
devices := []string{}
|
||||
_, domain, err := gomatrixserverlib.SplitID('@', ise.UserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the event is targeted locally then we want to expand the wildcard
|
||||
// out into individual device IDs so that we can send them to each respective
|
||||
// device. If the event isn't targeted locally then we can't expand the
|
||||
// wildcard as we don't know about the remote devices, so instead we leave it
|
||||
// as-is, so that the federation sender can send it on with the wildcard intact.
|
||||
if domain == t.ServerName && ise.DeviceID == "*" {
|
||||
var res userapi.QueryDevicesResponse
|
||||
err = t.UserAPI.QueryDevices(context.TODO(), &userapi.QueryDevicesRequest{
|
||||
UserID: ise.UserID,
|
||||
}, &res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, dev := range res.Devices {
|
||||
devices = append(devices, dev.ID)
|
||||
}
|
||||
} else {
|
||||
devices = append(devices, ise.DeviceID)
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"user_id": ise.UserID,
|
||||
"num_devices": len(devices),
|
||||
"type": ise.Type,
|
||||
}).Tracef("Producing to topic '%s'", t.OutputSendToDeviceEventTopic)
|
||||
for _, device := range devices {
|
||||
ote := &api.OutputSendToDeviceEvent{
|
||||
UserID: ise.UserID,
|
||||
DeviceID: device,
|
||||
SendToDeviceEvent: ise.SendToDeviceEvent,
|
||||
}
|
||||
|
||||
eventJSON, err := json.Marshal(ote)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("sendToDevice failed json.Marshal")
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = t.JetStream.PublishMsg(&nats.Msg{
|
||||
Subject: t.OutputSendToDeviceEventTopic,
|
||||
Data: eventJSON,
|
||||
}); err != nil {
|
||||
logrus.WithError(err).Error("sendToDevice failed t.Producer.SendMessage")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InputReceiptEvent implements api.EDUServerInputAPI
|
||||
// TODO: Intelligently batch requests sent by the same user (e.g wait a few milliseconds before emitting output events)
|
||||
func (t *EDUServerInputAPI) InputReceiptEvent(
|
||||
ctx context.Context,
|
||||
request *api.InputReceiptEventRequest,
|
||||
response *api.InputReceiptEventResponse,
|
||||
) error {
|
||||
logrus.WithFields(logrus.Fields{}).Tracef("Producing to topic '%s'", t.OutputReceiptEventTopic)
|
||||
output := &api.OutputReceiptEvent{
|
||||
UserID: request.InputReceiptEvent.UserID,
|
||||
RoomID: request.InputReceiptEvent.RoomID,
|
||||
EventID: request.InputReceiptEvent.EventID,
|
||||
Type: request.InputReceiptEvent.Type,
|
||||
Timestamp: request.InputReceiptEvent.Timestamp,
|
||||
}
|
||||
js, err := json.Marshal(output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = t.JetStream.PublishMsg(&nats.Msg{
|
||||
Subject: t.OutputReceiptEventTopic,
|
||||
Data: js,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
package inthttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/eduserver/api"
|
||||
"github.com/matrix-org/dendrite/internal/httputil"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
)
|
||||
|
||||
// HTTP paths for the internal HTTP APIs
|
||||
const (
|
||||
EDUServerInputTypingEventPath = "/eduserver/input"
|
||||
EDUServerInputSendToDeviceEventPath = "/eduserver/sendToDevice"
|
||||
EDUServerInputReceiptEventPath = "/eduserver/receipt"
|
||||
)
|
||||
|
||||
// NewEDUServerClient creates a EDUServerInputAPI implemented by talking to a HTTP POST API.
|
||||
func NewEDUServerClient(eduServerURL string, httpClient *http.Client) (api.EDUServerInputAPI, error) {
|
||||
if httpClient == nil {
|
||||
return nil, errors.New("NewEDUServerClient: httpClient is <nil>")
|
||||
}
|
||||
return &httpEDUServerInputAPI{eduServerURL, httpClient}, nil
|
||||
}
|
||||
|
||||
type httpEDUServerInputAPI struct {
|
||||
eduServerURL string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// InputTypingEvent implements EDUServerInputAPI
|
||||
func (h *httpEDUServerInputAPI) InputTypingEvent(
|
||||
ctx context.Context,
|
||||
request *api.InputTypingEventRequest,
|
||||
response *api.InputTypingEventResponse,
|
||||
) error {
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, "InputTypingEvent")
|
||||
defer span.Finish()
|
||||
|
||||
apiURL := h.eduServerURL + EDUServerInputTypingEventPath
|
||||
return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
|
||||
}
|
||||
|
||||
// InputSendToDeviceEvent implements EDUServerInputAPI
|
||||
func (h *httpEDUServerInputAPI) InputSendToDeviceEvent(
|
||||
ctx context.Context,
|
||||
request *api.InputSendToDeviceEventRequest,
|
||||
response *api.InputSendToDeviceEventResponse,
|
||||
) error {
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, "InputSendToDeviceEvent")
|
||||
defer span.Finish()
|
||||
|
||||
apiURL := h.eduServerURL + EDUServerInputSendToDeviceEventPath
|
||||
return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
|
||||
}
|
||||
|
||||
// InputSendToDeviceEvent implements EDUServerInputAPI
|
||||
func (h *httpEDUServerInputAPI) InputReceiptEvent(
|
||||
ctx context.Context,
|
||||
request *api.InputReceiptEventRequest,
|
||||
response *api.InputReceiptEventResponse,
|
||||
) error {
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, "InputReceiptEventPath")
|
||||
defer span.Finish()
|
||||
|
||||
apiURL := h.eduServerURL + EDUServerInputReceiptEventPath
|
||||
return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
package inthttp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/matrix-org/dendrite/eduserver/api"
|
||||
"github.com/matrix-org/dendrite/internal/httputil"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
// AddRoutes adds the EDUServerInputAPI handlers to the http.ServeMux.
|
||||
func AddRoutes(t api.EDUServerInputAPI, internalAPIMux *mux.Router) {
|
||||
internalAPIMux.Handle(EDUServerInputTypingEventPath,
|
||||
httputil.MakeInternalAPI("inputTypingEvents", func(req *http.Request) util.JSONResponse {
|
||||
var request api.InputTypingEventRequest
|
||||
var response api.InputTypingEventResponse
|
||||
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
|
||||
return util.MessageResponse(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
if err := t.InputTypingEvent(req.Context(), &request, &response); err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
||||
}),
|
||||
)
|
||||
internalAPIMux.Handle(EDUServerInputSendToDeviceEventPath,
|
||||
httputil.MakeInternalAPI("inputSendToDeviceEvents", func(req *http.Request) util.JSONResponse {
|
||||
var request api.InputSendToDeviceEventRequest
|
||||
var response api.InputSendToDeviceEventResponse
|
||||
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
|
||||
return util.MessageResponse(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
if err := t.InputSendToDeviceEvent(req.Context(), &request, &response); err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
||||
}),
|
||||
)
|
||||
internalAPIMux.Handle(EDUServerInputReceiptEventPath,
|
||||
httputil.MakeInternalAPI("inputReceiptEvent", func(req *http.Request) util.JSONResponse {
|
||||
var request api.InputReceiptEventRequest
|
||||
var response api.InputReceiptEventResponse
|
||||
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
|
||||
return util.MessageResponse(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
if err := t.InputReceiptEvent(req.Context(), &request, &response); err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,257 +0,0 @@
|
|||
// Copyright 2020 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/eduserver/api"
|
||||
"github.com/matrix-org/dendrite/federationapi/queue"
|
||||
"github.com/matrix-org/dendrite/federationapi/storage"
|
||||
"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/matrix-org/util"
|
||||
"github.com/nats-io/nats.go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// OutputEDUConsumer consumes events that originate in EDU server.
|
||||
type OutputEDUConsumer struct {
|
||||
ctx context.Context
|
||||
jetstream nats.JetStreamContext
|
||||
durable string
|
||||
db storage.Database
|
||||
queues *queue.OutgoingQueues
|
||||
ServerName gomatrixserverlib.ServerName
|
||||
typingTopic string
|
||||
sendToDeviceTopic string
|
||||
receiptTopic string
|
||||
}
|
||||
|
||||
// NewOutputEDUConsumer creates a new OutputEDUConsumer. Call Start() to begin consuming from EDU servers.
|
||||
func NewOutputEDUConsumer(
|
||||
process *process.ProcessContext,
|
||||
cfg *config.FederationAPI,
|
||||
js nats.JetStreamContext,
|
||||
queues *queue.OutgoingQueues,
|
||||
store storage.Database,
|
||||
) *OutputEDUConsumer {
|
||||
return &OutputEDUConsumer{
|
||||
ctx: process.Context(),
|
||||
jetstream: js,
|
||||
queues: queues,
|
||||
db: store,
|
||||
ServerName: cfg.Matrix.ServerName,
|
||||
durable: cfg.Matrix.JetStream.Durable("FederationAPIEDUServerConsumer"),
|
||||
typingTopic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputTypingEvent),
|
||||
sendToDeviceTopic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputSendToDeviceEvent),
|
||||
receiptTopic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputReceiptEvent),
|
||||
}
|
||||
}
|
||||
|
||||
// Start consuming from EDU servers
|
||||
func (t *OutputEDUConsumer) Start() error {
|
||||
if err := jetstream.JetStreamConsumer(
|
||||
t.ctx, t.jetstream, t.typingTopic, t.durable, t.onTypingEvent,
|
||||
nats.DeliverAll(), nats.ManualAck(),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := jetstream.JetStreamConsumer(
|
||||
t.ctx, t.jetstream, t.sendToDeviceTopic, t.durable, t.onSendToDeviceEvent,
|
||||
nats.DeliverAll(), nats.ManualAck(),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := jetstream.JetStreamConsumer(
|
||||
t.ctx, t.jetstream, t.receiptTopic, t.durable, t.onReceiptEvent,
|
||||
nats.DeliverAll(), nats.ManualAck(),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// onSendToDeviceEvent is called in response to a message received on the
|
||||
// send-to-device events topic from the EDU server.
|
||||
func (t *OutputEDUConsumer) onSendToDeviceEvent(ctx context.Context, msg *nats.Msg) bool {
|
||||
// Extract the send-to-device event from msg.
|
||||
var ote api.OutputSendToDeviceEvent
|
||||
if err := json.Unmarshal(msg.Data, &ote); err != nil {
|
||||
log.WithError(err).Errorf("eduserver output log: message parse failed (expected send-to-device)")
|
||||
return true
|
||||
}
|
||||
|
||||
// only send send-to-device events which originated from us
|
||||
_, originServerName, err := gomatrixserverlib.SplitID('@', ote.Sender)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("user_id", ote.Sender).Error("Failed to extract domain from send-to-device sender")
|
||||
return true
|
||||
}
|
||||
if originServerName != t.ServerName {
|
||||
log.WithField("other_server", originServerName).Info("Suppressing send-to-device: originated elsewhere")
|
||||
return true
|
||||
}
|
||||
|
||||
_, destServerName, err := gomatrixserverlib.SplitID('@', ote.UserID)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("user_id", ote.UserID).Error("Failed to extract domain from send-to-device destination")
|
||||
return true
|
||||
}
|
||||
|
||||
// Pack the EDU and marshal it
|
||||
edu := &gomatrixserverlib.EDU{
|
||||
Type: gomatrixserverlib.MDirectToDevice,
|
||||
Origin: string(t.ServerName),
|
||||
}
|
||||
tdm := gomatrixserverlib.ToDeviceMessage{
|
||||
Sender: ote.Sender,
|
||||
Type: ote.Type,
|
||||
MessageID: util.RandomString(32),
|
||||
Messages: map[string]map[string]json.RawMessage{
|
||||
ote.UserID: {
|
||||
ote.DeviceID: ote.Content,
|
||||
},
|
||||
},
|
||||
}
|
||||
if edu.Content, err = json.Marshal(tdm); err != nil {
|
||||
log.WithError(err).Error("failed to marshal EDU JSON")
|
||||
return true
|
||||
}
|
||||
|
||||
log.Debugf("Sending send-to-device message into %q destination queue", destServerName)
|
||||
if err := t.queues.SendEDU(edu, t.ServerName, []gomatrixserverlib.ServerName{destServerName}); err != nil {
|
||||
log.WithError(err).Error("failed to send EDU")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// onTypingEvent is called in response to a message received on the typing
|
||||
// events topic from the EDU server.
|
||||
func (t *OutputEDUConsumer) onTypingEvent(ctx context.Context, msg *nats.Msg) bool {
|
||||
// Extract the typing event from msg.
|
||||
var ote api.OutputTypingEvent
|
||||
if err := json.Unmarshal(msg.Data, &ote); err != nil {
|
||||
// Skip this msg but continue processing messages.
|
||||
log.WithError(err).Errorf("eduserver output log: message parse failed (expected typing)")
|
||||
_ = msg.Ack()
|
||||
return true
|
||||
}
|
||||
|
||||
// only send typing events which originated from us
|
||||
_, typingServerName, err := gomatrixserverlib.SplitID('@', ote.Event.UserID)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("user_id", ote.Event.UserID).Error("Failed to extract domain from typing sender")
|
||||
_ = msg.Ack()
|
||||
return true
|
||||
}
|
||||
if typingServerName != t.ServerName {
|
||||
return true
|
||||
}
|
||||
|
||||
joined, err := t.db.GetJoinedHosts(ctx, ote.Event.RoomID)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("room_id", ote.Event.RoomID).Error("failed to get joined hosts for room")
|
||||
return false
|
||||
}
|
||||
|
||||
names := make([]gomatrixserverlib.ServerName, len(joined))
|
||||
for i := range joined {
|
||||
names[i] = joined[i].ServerName
|
||||
}
|
||||
|
||||
edu := &gomatrixserverlib.EDU{Type: ote.Event.Type}
|
||||
if edu.Content, err = json.Marshal(map[string]interface{}{
|
||||
"room_id": ote.Event.RoomID,
|
||||
"user_id": ote.Event.UserID,
|
||||
"typing": ote.Event.Typing,
|
||||
}); err != nil {
|
||||
log.WithError(err).Error("failed to marshal EDU JSON")
|
||||
return true
|
||||
}
|
||||
|
||||
if err := t.queues.SendEDU(edu, t.ServerName, names); err != nil {
|
||||
log.WithError(err).Error("failed to send EDU")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// onReceiptEvent is called in response to a message received on the receipt
|
||||
// events topic from the EDU server.
|
||||
func (t *OutputEDUConsumer) onReceiptEvent(ctx context.Context, msg *nats.Msg) bool {
|
||||
// Extract the typing event from msg.
|
||||
var receipt api.OutputReceiptEvent
|
||||
if err := json.Unmarshal(msg.Data, &receipt); err != nil {
|
||||
// Skip this msg but continue processing messages.
|
||||
log.WithError(err).Errorf("eduserver output log: message parse failed (expected receipt)")
|
||||
return true
|
||||
}
|
||||
|
||||
// only send receipt events which originated from us
|
||||
_, receiptServerName, err := gomatrixserverlib.SplitID('@', receipt.UserID)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("user_id", receipt.UserID).Error("failed to extract domain from receipt sender")
|
||||
return true
|
||||
}
|
||||
if receiptServerName != t.ServerName {
|
||||
return true
|
||||
}
|
||||
|
||||
joined, err := t.db.GetJoinedHosts(ctx, receipt.RoomID)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("room_id", receipt.RoomID).Error("failed to get joined hosts for room")
|
||||
return false
|
||||
}
|
||||
|
||||
names := make([]gomatrixserverlib.ServerName, len(joined))
|
||||
for i := range joined {
|
||||
names[i] = joined[i].ServerName
|
||||
}
|
||||
|
||||
content := map[string]api.FederationReceiptMRead{}
|
||||
content[receipt.RoomID] = api.FederationReceiptMRead{
|
||||
User: map[string]api.FederationReceiptData{
|
||||
receipt.UserID: {
|
||||
Data: api.ReceiptTS{
|
||||
TS: receipt.Timestamp,
|
||||
},
|
||||
EventIDs: []string{receipt.EventID},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
edu := &gomatrixserverlib.EDU{
|
||||
Type: gomatrixserverlib.MReceipt,
|
||||
Origin: string(t.ServerName),
|
||||
}
|
||||
if edu.Content, err = json.Marshal(content); err != nil {
|
||||
log.WithError(err).Error("failed to marshal EDU JSON")
|
||||
return true
|
||||
}
|
||||
|
||||
if err := t.queues.SendEDU(edu, t.ServerName, names); err != nil {
|
||||
log.WithError(err).Error("failed to send EDU")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
@ -18,9 +18,9 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
|
||||
eduserverAPI "github.com/matrix-org/dendrite/eduserver/api"
|
||||
"github.com/matrix-org/dendrite/federationapi/queue"
|
||||
"github.com/matrix-org/dendrite/federationapi/storage"
|
||||
"github.com/matrix-org/dendrite/federationapi/types"
|
||||
"github.com/matrix-org/dendrite/keyserver/api"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
|
|
@ -190,7 +190,7 @@ func (t *KeyChangeConsumer) onCrossSigningMessage(m api.DeviceMessage) bool {
|
|||
|
||||
// Pack the EDU and marshal it
|
||||
edu := &gomatrixserverlib.EDU{
|
||||
Type: eduserverAPI.MSigningKeyUpdate,
|
||||
Type: types.MSigningKeyUpdate,
|
||||
Origin: string(t.serverName),
|
||||
}
|
||||
if edu.Content, err = json.Marshal(output); err != nil {
|
||||
|
|
|
|||
143
federationapi/consumers/presence.go
Normal file
143
federationapi/consumers/presence.go
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
// 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"
|
||||
"strconv"
|
||||
|
||||
"github.com/matrix-org/dendrite/federationapi/queue"
|
||||
"github.com/matrix-org/dendrite/federationapi/storage"
|
||||
fedTypes "github.com/matrix-org/dendrite/federationapi/types"
|
||||
"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/syncapi/types"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/nats-io/nats.go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// OutputReceiptConsumer consumes events that originate in the clientapi.
|
||||
type OutputPresenceConsumer struct {
|
||||
ctx context.Context
|
||||
jetstream nats.JetStreamContext
|
||||
durable string
|
||||
db storage.Database
|
||||
queues *queue.OutgoingQueues
|
||||
ServerName gomatrixserverlib.ServerName
|
||||
topic string
|
||||
outboundPresenceEnabled bool
|
||||
}
|
||||
|
||||
// NewOutputPresenceConsumer creates a new OutputPresenceConsumer. Call Start() to begin consuming events.
|
||||
func NewOutputPresenceConsumer(
|
||||
process *process.ProcessContext,
|
||||
cfg *config.FederationAPI,
|
||||
js nats.JetStreamContext,
|
||||
queues *queue.OutgoingQueues,
|
||||
store storage.Database,
|
||||
) *OutputPresenceConsumer {
|
||||
return &OutputPresenceConsumer{
|
||||
ctx: process.Context(),
|
||||
jetstream: js,
|
||||
queues: queues,
|
||||
db: store,
|
||||
ServerName: cfg.Matrix.ServerName,
|
||||
durable: cfg.Matrix.JetStream.Durable("FederationAPIPresenceConsumer"),
|
||||
topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputPresenceEvent),
|
||||
outboundPresenceEnabled: cfg.Matrix.Presence.EnableOutbound,
|
||||
}
|
||||
}
|
||||
|
||||
// Start consuming from the clientapi
|
||||
func (t *OutputPresenceConsumer) Start() error {
|
||||
if !t.outboundPresenceEnabled {
|
||||
return nil
|
||||
}
|
||||
return jetstream.JetStreamConsumer(
|
||||
t.ctx, t.jetstream, t.topic, t.durable, t.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(), nats.HeadersOnly(),
|
||||
)
|
||||
}
|
||||
|
||||
// onMessage is called in response to a message received on the presence
|
||||
// events topic from the client api.
|
||||
func (t *OutputPresenceConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
// only send presence events which originated from us
|
||||
userID := msg.Header.Get(jetstream.UserID)
|
||||
_, serverName, err := gomatrixserverlib.SplitID('@', userID)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("user_id", userID).Error("failed to extract domain from receipt sender")
|
||||
return true
|
||||
}
|
||||
if serverName != t.ServerName {
|
||||
return true
|
||||
}
|
||||
|
||||
presence := msg.Header.Get("presence")
|
||||
|
||||
ts, err := strconv.Atoi(msg.Header.Get("last_active_ts"))
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
joined, err := t.db.GetAllJoinedHosts(ctx)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failed to get joined hosts")
|
||||
return true
|
||||
}
|
||||
if len(joined) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
var statusMsg *string = nil
|
||||
if data, ok := msg.Header["status_msg"]; ok && len(data) > 0 {
|
||||
status := msg.Header.Get("status_msg")
|
||||
statusMsg = &status
|
||||
}
|
||||
|
||||
p := types.PresenceInternal{LastActiveTS: gomatrixserverlib.Timestamp(ts)}
|
||||
|
||||
content := fedTypes.Presence{
|
||||
Push: []fedTypes.PresenceContent{
|
||||
{
|
||||
CurrentlyActive: p.CurrentlyActive(),
|
||||
LastActiveAgo: p.LastActiveAgo(),
|
||||
Presence: presence,
|
||||
StatusMsg: statusMsg,
|
||||
UserID: userID,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
edu := &gomatrixserverlib.EDU{
|
||||
Type: gomatrixserverlib.MPresence,
|
||||
Origin: string(t.ServerName),
|
||||
}
|
||||
if edu.Content, err = json.Marshal(content); err != nil {
|
||||
log.WithError(err).Error("failed to marshal EDU JSON")
|
||||
return true
|
||||
}
|
||||
|
||||
log.Debugf("sending presence EDU to %d servers", len(joined))
|
||||
if err = t.queues.SendEDU(edu, t.ServerName, joined); err != nil {
|
||||
log.WithError(err).Error("failed to send EDU")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
141
federationapi/consumers/receipts.go
Normal file
141
federationapi/consumers/receipts.go
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
// 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"
|
||||
"strconv"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/matrix-org/dendrite/federationapi/queue"
|
||||
"github.com/matrix-org/dendrite/federationapi/storage"
|
||||
fedTypes "github.com/matrix-org/dendrite/federationapi/types"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
syncTypes "github.com/matrix-org/dendrite/syncapi/types"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/nats-io/nats.go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// OutputReceiptConsumer consumes events that originate in the clientapi.
|
||||
type OutputReceiptConsumer struct {
|
||||
ctx context.Context
|
||||
jetstream nats.JetStreamContext
|
||||
durable string
|
||||
db storage.Database
|
||||
queues *queue.OutgoingQueues
|
||||
ServerName gomatrixserverlib.ServerName
|
||||
topic string
|
||||
}
|
||||
|
||||
// NewOutputReceiptConsumer creates a new OutputReceiptConsumer. Call Start() to begin consuming typing events.
|
||||
func NewOutputReceiptConsumer(
|
||||
process *process.ProcessContext,
|
||||
cfg *config.FederationAPI,
|
||||
js nats.JetStreamContext,
|
||||
queues *queue.OutgoingQueues,
|
||||
store storage.Database,
|
||||
) *OutputReceiptConsumer {
|
||||
return &OutputReceiptConsumer{
|
||||
ctx: process.Context(),
|
||||
jetstream: js,
|
||||
queues: queues,
|
||||
db: store,
|
||||
ServerName: cfg.Matrix.ServerName,
|
||||
durable: cfg.Matrix.JetStream.Durable("FederationAPIReceiptConsumer"),
|
||||
topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputReceiptEvent),
|
||||
}
|
||||
}
|
||||
|
||||
// Start consuming from the clientapi
|
||||
func (t *OutputReceiptConsumer) Start() error {
|
||||
return jetstream.JetStreamConsumer(
|
||||
t.ctx, t.jetstream, t.topic, t.durable, t.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(), nats.HeadersOnly(),
|
||||
)
|
||||
}
|
||||
|
||||
// onMessage is called in response to a message received on the receipt
|
||||
// events topic from the client api.
|
||||
func (t *OutputReceiptConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
receipt := syncTypes.OutputReceiptEvent{
|
||||
UserID: msg.Header.Get(jetstream.UserID),
|
||||
RoomID: msg.Header.Get(jetstream.RoomID),
|
||||
EventID: msg.Header.Get(jetstream.EventID),
|
||||
Type: msg.Header.Get("type"),
|
||||
}
|
||||
|
||||
// only send receipt events which originated from us
|
||||
_, receiptServerName, err := gomatrixserverlib.SplitID('@', receipt.UserID)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("user_id", receipt.UserID).Error("failed to extract domain from receipt sender")
|
||||
return true
|
||||
}
|
||||
if receiptServerName != t.ServerName {
|
||||
return true
|
||||
}
|
||||
|
||||
timestamp, err := strconv.Atoi(msg.Header.Get("timestamp"))
|
||||
if err != nil {
|
||||
// If the message was invalid, log it and move on to the next message in the stream
|
||||
log.WithError(err).Errorf("EDU output log: message parse failure")
|
||||
sentry.CaptureException(err)
|
||||
return true
|
||||
}
|
||||
|
||||
receipt.Timestamp = gomatrixserverlib.Timestamp(timestamp)
|
||||
|
||||
joined, err := t.db.GetJoinedHosts(ctx, receipt.RoomID)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("room_id", receipt.RoomID).Error("failed to get joined hosts for room")
|
||||
return false
|
||||
}
|
||||
|
||||
names := make([]gomatrixserverlib.ServerName, len(joined))
|
||||
for i := range joined {
|
||||
names[i] = joined[i].ServerName
|
||||
}
|
||||
|
||||
content := map[string]fedTypes.FederationReceiptMRead{}
|
||||
content[receipt.RoomID] = fedTypes.FederationReceiptMRead{
|
||||
User: map[string]fedTypes.FederationReceiptData{
|
||||
receipt.UserID: {
|
||||
Data: fedTypes.ReceiptTS{
|
||||
TS: receipt.Timestamp,
|
||||
},
|
||||
EventIDs: []string{receipt.EventID},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
edu := &gomatrixserverlib.EDU{
|
||||
Type: gomatrixserverlib.MReceipt,
|
||||
Origin: string(t.ServerName),
|
||||
}
|
||||
if edu.Content, err = json.Marshal(content); err != nil {
|
||||
log.WithError(err).Error("failed to marshal EDU JSON")
|
||||
return true
|
||||
}
|
||||
|
||||
if err := t.queues.SendEDU(edu, t.ServerName, names); err != nil {
|
||||
log.WithError(err).Error("failed to send EDU")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
125
federationapi/consumers/sendtodevice.go
Normal file
125
federationapi/consumers/sendtodevice.go
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
// 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/federationapi/queue"
|
||||
"github.com/matrix-org/dendrite/federationapi/storage"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
syncTypes "github.com/matrix-org/dendrite/syncapi/types"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/nats-io/nats.go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// OutputSendToDeviceConsumer consumes events that originate in the clientapi.
|
||||
type OutputSendToDeviceConsumer struct {
|
||||
ctx context.Context
|
||||
jetstream nats.JetStreamContext
|
||||
durable string
|
||||
db storage.Database
|
||||
queues *queue.OutgoingQueues
|
||||
ServerName gomatrixserverlib.ServerName
|
||||
topic string
|
||||
}
|
||||
|
||||
// NewOutputSendToDeviceConsumer creates a new OutputSendToDeviceConsumer. Call Start() to begin consuming send-to-device events.
|
||||
func NewOutputSendToDeviceConsumer(
|
||||
process *process.ProcessContext,
|
||||
cfg *config.FederationAPI,
|
||||
js nats.JetStreamContext,
|
||||
queues *queue.OutgoingQueues,
|
||||
store storage.Database,
|
||||
) *OutputSendToDeviceConsumer {
|
||||
return &OutputSendToDeviceConsumer{
|
||||
ctx: process.Context(),
|
||||
jetstream: js,
|
||||
queues: queues,
|
||||
db: store,
|
||||
ServerName: cfg.Matrix.ServerName,
|
||||
durable: cfg.Matrix.JetStream.Durable("FederationAPIESendToDeviceConsumer"),
|
||||
topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputSendToDeviceEvent),
|
||||
}
|
||||
}
|
||||
|
||||
// Start consuming from the client api
|
||||
func (t *OutputSendToDeviceConsumer) 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
|
||||
// send-to-device events topic from the client api.
|
||||
func (t *OutputSendToDeviceConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
// only send send-to-device events which originated from us
|
||||
sender := msg.Header.Get("sender")
|
||||
_, originServerName, err := gomatrixserverlib.SplitID('@', sender)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("user_id", sender).Error("Failed to extract domain from send-to-device sender")
|
||||
return true
|
||||
}
|
||||
if originServerName != t.ServerName {
|
||||
log.WithField("other_server", originServerName).Info("Suppressing send-to-device: originated elsewhere")
|
||||
return true
|
||||
}
|
||||
// Extract the send-to-device event from msg.
|
||||
var ote syncTypes.OutputSendToDeviceEvent
|
||||
if err = json.Unmarshal(msg.Data, &ote); err != nil {
|
||||
log.WithError(err).Errorf("output log: message parse failed (expected send-to-device)")
|
||||
return true
|
||||
}
|
||||
|
||||
_, destServerName, err := gomatrixserverlib.SplitID('@', ote.UserID)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("user_id", ote.UserID).Error("Failed to extract domain from send-to-device destination")
|
||||
return true
|
||||
}
|
||||
|
||||
// Pack the EDU and marshal it
|
||||
edu := &gomatrixserverlib.EDU{
|
||||
Type: gomatrixserverlib.MDirectToDevice,
|
||||
Origin: string(t.ServerName),
|
||||
}
|
||||
tdm := gomatrixserverlib.ToDeviceMessage{
|
||||
Sender: ote.Sender,
|
||||
Type: ote.Type,
|
||||
MessageID: util.RandomString(32),
|
||||
Messages: map[string]map[string]json.RawMessage{
|
||||
ote.UserID: {
|
||||
ote.DeviceID: ote.Content,
|
||||
},
|
||||
},
|
||||
}
|
||||
if edu.Content, err = json.Marshal(tdm); err != nil {
|
||||
log.WithError(err).Error("failed to marshal EDU JSON")
|
||||
return true
|
||||
}
|
||||
|
||||
log.Debugf("Sending send-to-device message into %q destination queue", destServerName)
|
||||
if err := t.queues.SendEDU(edu, t.ServerName, []gomatrixserverlib.ServerName{destServerName}); err != nil {
|
||||
log.WithError(err).Error("failed to send EDU")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
119
federationapi/consumers/typing.go
Normal file
119
federationapi/consumers/typing.go
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
// 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"
|
||||
"strconv"
|
||||
|
||||
"github.com/matrix-org/dendrite/federationapi/queue"
|
||||
"github.com/matrix-org/dendrite/federationapi/storage"
|
||||
"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"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// OutputTypingConsumer consumes events that originate in the clientapi.
|
||||
type OutputTypingConsumer struct {
|
||||
ctx context.Context
|
||||
jetstream nats.JetStreamContext
|
||||
durable string
|
||||
db storage.Database
|
||||
queues *queue.OutgoingQueues
|
||||
ServerName gomatrixserverlib.ServerName
|
||||
topic string
|
||||
}
|
||||
|
||||
// NewOutputTypingConsumer creates a new OutputTypingConsumer. Call Start() to begin consuming typing events.
|
||||
func NewOutputTypingConsumer(
|
||||
process *process.ProcessContext,
|
||||
cfg *config.FederationAPI,
|
||||
js nats.JetStreamContext,
|
||||
queues *queue.OutgoingQueues,
|
||||
store storage.Database,
|
||||
) *OutputTypingConsumer {
|
||||
return &OutputTypingConsumer{
|
||||
ctx: process.Context(),
|
||||
jetstream: js,
|
||||
queues: queues,
|
||||
db: store,
|
||||
ServerName: cfg.Matrix.ServerName,
|
||||
durable: cfg.Matrix.JetStream.Durable("FederationAPITypingConsumer"),
|
||||
topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputTypingEvent),
|
||||
}
|
||||
}
|
||||
|
||||
// Start consuming from the clientapi
|
||||
func (t *OutputTypingConsumer) Start() error {
|
||||
return jetstream.JetStreamConsumer(
|
||||
t.ctx, t.jetstream, t.topic, t.durable, t.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(), nats.HeadersOnly(),
|
||||
)
|
||||
}
|
||||
|
||||
// onMessage is called in response to a message received on the typing
|
||||
// events topic from the client api.
|
||||
func (t *OutputTypingConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
// Extract the typing event from msg.
|
||||
roomID := msg.Header.Get(jetstream.RoomID)
|
||||
userID := msg.Header.Get(jetstream.UserID)
|
||||
typing, err := strconv.ParseBool(msg.Header.Get("typing"))
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("EDU output log: typing parse failure")
|
||||
return true
|
||||
}
|
||||
|
||||
// only send typing events which originated from us
|
||||
_, typingServerName, err := gomatrixserverlib.SplitID('@', userID)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("user_id", userID).Error("Failed to extract domain from typing sender")
|
||||
_ = msg.Ack()
|
||||
return true
|
||||
}
|
||||
if typingServerName != t.ServerName {
|
||||
return true
|
||||
}
|
||||
|
||||
joined, err := t.db.GetJoinedHosts(ctx, roomID)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("room_id", roomID).Error("failed to get joined hosts for room")
|
||||
return false
|
||||
}
|
||||
|
||||
names := make([]gomatrixserverlib.ServerName, len(joined))
|
||||
for i := range joined {
|
||||
names[i] = joined[i].ServerName
|
||||
}
|
||||
|
||||
edu := &gomatrixserverlib.EDU{Type: "m.typing"}
|
||||
if edu.Content, err = json.Marshal(map[string]interface{}{
|
||||
"room_id": roomID,
|
||||
"user_id": userID,
|
||||
"typing": typing,
|
||||
}); err != nil {
|
||||
log.WithError(err).Error("failed to marshal EDU JSON")
|
||||
return true
|
||||
}
|
||||
if err := t.queues.SendEDU(edu, t.ServerName, names); err != nil {
|
||||
log.WithError(err).Error("failed to send EDU")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
@ -16,12 +16,12 @@ package federationapi
|
|||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
eduserverAPI "github.com/matrix-org/dendrite/eduserver/api"
|
||||
"github.com/matrix-org/dendrite/federationapi/api"
|
||||
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
||||
"github.com/matrix-org/dendrite/federationapi/consumers"
|
||||
"github.com/matrix-org/dendrite/federationapi/internal"
|
||||
"github.com/matrix-org/dendrite/federationapi/inthttp"
|
||||
"github.com/matrix-org/dendrite/federationapi/producers"
|
||||
"github.com/matrix-org/dendrite/federationapi/queue"
|
||||
"github.com/matrix-org/dendrite/federationapi/statistics"
|
||||
"github.com/matrix-org/dendrite/federationapi/storage"
|
||||
|
|
@ -31,6 +31,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/setup/base"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
|
|
@ -46,6 +47,7 @@ func AddInternalRoutes(router *mux.Router, intAPI api.FederationInternalAPI) {
|
|||
|
||||
// AddPublicRoutes sets up and registers HTTP handlers on the base API muxes for the FederationAPI component.
|
||||
func AddPublicRoutes(
|
||||
process *process.ProcessContext,
|
||||
fedRouter, keyRouter, wellKnownRouter *mux.Router,
|
||||
cfg *config.FederationAPI,
|
||||
userAPI userapi.UserInternalAPI,
|
||||
|
|
@ -53,16 +55,27 @@ func AddPublicRoutes(
|
|||
keyRing gomatrixserverlib.JSONVerifier,
|
||||
rsAPI roomserverAPI.RoomserverInternalAPI,
|
||||
federationAPI federationAPI.FederationInternalAPI,
|
||||
eduAPI eduserverAPI.EDUServerInputAPI,
|
||||
keyAPI keyserverAPI.KeyInternalAPI,
|
||||
mscCfg *config.MSCs,
|
||||
servers federationAPI.ServersInRoomProvider,
|
||||
) {
|
||||
|
||||
js, _ := jetstream.Prepare(process, &cfg.Matrix.JetStream)
|
||||
producer := &producers.SyncAPIProducer{
|
||||
JetStream: js,
|
||||
TopicReceiptEvent: cfg.Matrix.JetStream.Prefixed(jetstream.OutputReceiptEvent),
|
||||
TopicSendToDeviceEvent: cfg.Matrix.JetStream.Prefixed(jetstream.OutputSendToDeviceEvent),
|
||||
TopicTypingEvent: cfg.Matrix.JetStream.Prefixed(jetstream.OutputTypingEvent),
|
||||
TopicPresenceEvent: cfg.Matrix.JetStream.Prefixed(jetstream.OutputPresenceEvent),
|
||||
ServerName: cfg.Matrix.ServerName,
|
||||
UserAPI: userAPI,
|
||||
}
|
||||
|
||||
routing.Setup(
|
||||
fedRouter, keyRouter, wellKnownRouter, cfg, rsAPI,
|
||||
eduAPI, federationAPI, keyRing,
|
||||
federationAPI, keyRing,
|
||||
federation, userAPI, keyAPI, mscCfg,
|
||||
servers,
|
||||
servers, producer,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -112,19 +125,36 @@ func NewInternalAPI(
|
|||
if err = rsConsumer.Start(); err != nil {
|
||||
logrus.WithError(err).Panic("failed to start room server consumer")
|
||||
}
|
||||
|
||||
tsConsumer := consumers.NewOutputEDUConsumer(
|
||||
tsConsumer := consumers.NewOutputSendToDeviceConsumer(
|
||||
base.ProcessContext, cfg, js, queues, federationDB,
|
||||
)
|
||||
if err := tsConsumer.Start(); err != nil {
|
||||
logrus.WithError(err).Panic("failed to start typing server consumer")
|
||||
if err = tsConsumer.Start(); err != nil {
|
||||
logrus.WithError(err).Panic("failed to start send-to-device consumer")
|
||||
}
|
||||
receiptConsumer := consumers.NewOutputReceiptConsumer(
|
||||
base.ProcessContext, cfg, js, queues, federationDB,
|
||||
)
|
||||
if err = receiptConsumer.Start(); err != nil {
|
||||
logrus.WithError(err).Panic("failed to start receipt consumer")
|
||||
}
|
||||
typingConsumer := consumers.NewOutputTypingConsumer(
|
||||
base.ProcessContext, cfg, js, queues, federationDB,
|
||||
)
|
||||
if err = typingConsumer.Start(); err != nil {
|
||||
logrus.WithError(err).Panic("failed to start typing consumer")
|
||||
}
|
||||
keyConsumer := consumers.NewKeyChangeConsumer(
|
||||
base.ProcessContext, &base.Cfg.KeyServer, js, queues, federationDB, rsAPI,
|
||||
)
|
||||
if err := keyConsumer.Start(); err != nil {
|
||||
if err = keyConsumer.Start(); err != nil {
|
||||
logrus.WithError(err).Panic("failed to start key server consumer")
|
||||
}
|
||||
|
||||
presenceConsumer := consumers.NewOutputPresenceConsumer(
|
||||
base.ProcessContext, cfg, js, queues, federationDB,
|
||||
)
|
||||
if err = presenceConsumer.Start(); err != nil {
|
||||
logrus.WithError(err).Panic("failed to start presence consumer")
|
||||
}
|
||||
return internal.NewFederationInternalAPI(federationDB, cfg, rsAPI, federation, stats, caches, queues, keyRing)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ func TestRoomsV3URLEscapeDoNot404(t *testing.T) {
|
|||
fsAPI := base.FederationAPIHTTPClient()
|
||||
// TODO: This is pretty fragile, as if anything calls anything on these nils this test will break.
|
||||
// Unfortunately, it makes little sense to instantiate these dependencies when we just want to test routing.
|
||||
federationapi.AddPublicRoutes(base.PublicFederationAPIMux, base.PublicKeyAPIMux, base.PublicWellKnownAPIMux, &cfg.FederationAPI, nil, nil, keyRing, nil, fsAPI, nil, nil, &cfg.MSCs, nil)
|
||||
federationapi.AddPublicRoutes(base.ProcessContext, base.PublicFederationAPIMux, base.PublicKeyAPIMux, base.PublicWellKnownAPIMux, &cfg.FederationAPI, nil, nil, keyRing, nil, fsAPI, nil, &cfg.MSCs, nil)
|
||||
baseURL, cancel := test.ListenAndServe(t, base.PublicFederationAPIMux, true)
|
||||
defer cancel()
|
||||
serverName := gomatrixserverlib.ServerName(strings.TrimPrefix(baseURL, "https://"))
|
||||
|
|
|
|||
|
|
@ -392,17 +392,17 @@ func (r *FederationInternalAPI) performOutboundPeekUsingServer(
|
|||
|
||||
// we have the peek state now so let's process regardless of whether upstream gives up
|
||||
ctx = context.Background()
|
||||
|
||||
respState := respPeek.ToRespState()
|
||||
authEvents := respState.AuthEvents.UntrustedEvents(respPeek.RoomVersion)
|
||||
|
||||
// authenticate the state returned (check its auth events etc)
|
||||
// the equivalent of CheckSendJoinResponse()
|
||||
authEvents, _, err := respState.Check(ctx, respPeek.RoomVersion, r.keyRing, federatedAuthProvider(ctx, r.federation, r.keyRing, serverName))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error checking state returned from peeking: %w", err)
|
||||
}
|
||||
if err = sanityCheckAuthChain(authEvents); err != nil {
|
||||
return fmt.Errorf("sanityCheckAuthChain: %w", err)
|
||||
}
|
||||
if err = respState.Check(ctx, respPeek.RoomVersion, r.keyRing, federatedAuthProvider(ctx, r.federation, r.keyRing, serverName)); err != nil {
|
||||
return fmt.Errorf("error checking state returned from peeking: %w", err)
|
||||
}
|
||||
|
||||
// If we've got this far, the remote server is peeking.
|
||||
if renewing {
|
||||
|
|
|
|||
163
federationapi/producers/syncapi.go
Normal file
163
federationapi/producers/syncapi.go
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
// 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 (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||
"github.com/matrix-org/dendrite/syncapi/types"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/nats-io/nats.go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// SyncAPIProducer produces events for the sync API server to consume
|
||||
type SyncAPIProducer struct {
|
||||
TopicReceiptEvent string
|
||||
TopicSendToDeviceEvent string
|
||||
TopicTypingEvent string
|
||||
TopicPresenceEvent string
|
||||
JetStream nats.JetStreamContext
|
||||
ServerName gomatrixserverlib.ServerName
|
||||
UserAPI userapi.UserInternalAPI
|
||||
}
|
||||
|
||||
func (p *SyncAPIProducer) SendReceipt(
|
||||
ctx context.Context,
|
||||
userID, roomID, eventID, receiptType string, timestamp gomatrixserverlib.Timestamp,
|
||||
) error {
|
||||
m := &nats.Msg{
|
||||
Subject: p.TopicReceiptEvent,
|
||||
Header: nats.Header{},
|
||||
}
|
||||
m.Header.Set(jetstream.UserID, userID)
|
||||
m.Header.Set(jetstream.RoomID, roomID)
|
||||
m.Header.Set(jetstream.EventID, eventID)
|
||||
m.Header.Set("type", receiptType)
|
||||
m.Header.Set("timestamp", strconv.Itoa(int(timestamp)))
|
||||
|
||||
log.WithFields(log.Fields{}).Tracef("Producing to topic '%s'", p.TopicReceiptEvent)
|
||||
_, err := p.JetStream.PublishMsg(m, nats.Context(ctx))
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *SyncAPIProducer) SendToDevice(
|
||||
ctx context.Context, sender, userID, deviceID, eventType string,
|
||||
message interface{},
|
||||
) error {
|
||||
devices := []string{}
|
||||
_, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the event is targeted locally then we want to expand the wildcard
|
||||
// out into individual device IDs so that we can send them to each respective
|
||||
// device. If the event isn't targeted locally then we can't expand the
|
||||
// wildcard as we don't know about the remote devices, so instead we leave it
|
||||
// as-is, so that the federation sender can send it on with the wildcard intact.
|
||||
if domain == p.ServerName && deviceID == "*" {
|
||||
var res userapi.QueryDevicesResponse
|
||||
err = p.UserAPI.QueryDevices(context.TODO(), &userapi.QueryDevicesRequest{
|
||||
UserID: userID,
|
||||
}, &res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, dev := range res.Devices {
|
||||
devices = append(devices, dev.ID)
|
||||
}
|
||||
} else {
|
||||
devices = append(devices, deviceID)
|
||||
}
|
||||
|
||||
js, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"user_id": userID,
|
||||
"num_devices": len(devices),
|
||||
"type": eventType,
|
||||
}).Tracef("Producing to topic '%s'", p.TopicSendToDeviceEvent)
|
||||
for _, device := range devices {
|
||||
ote := &types.OutputSendToDeviceEvent{
|
||||
UserID: userID,
|
||||
DeviceID: device,
|
||||
SendToDeviceEvent: gomatrixserverlib.SendToDeviceEvent{
|
||||
Sender: sender,
|
||||
Type: eventType,
|
||||
Content: js,
|
||||
},
|
||||
}
|
||||
|
||||
eventJSON, err := json.Marshal(ote)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("sendToDevice failed json.Marshal")
|
||||
return err
|
||||
}
|
||||
m := &nats.Msg{
|
||||
Subject: p.TopicSendToDeviceEvent,
|
||||
Data: eventJSON,
|
||||
Header: nats.Header{},
|
||||
}
|
||||
m.Header.Set("sender", sender)
|
||||
m.Header.Set(jetstream.UserID, userID)
|
||||
|
||||
if _, err = p.JetStream.PublishMsg(m, nats.Context(ctx)); err != nil {
|
||||
log.WithError(err).Error("sendToDevice failed t.Producer.SendMessage")
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *SyncAPIProducer) SendTyping(
|
||||
ctx context.Context, userID, roomID string, typing bool, timeoutMS int64,
|
||||
) error {
|
||||
m := &nats.Msg{
|
||||
Subject: p.TopicTypingEvent,
|
||||
Header: nats.Header{},
|
||||
}
|
||||
m.Header.Set(jetstream.UserID, userID)
|
||||
m.Header.Set(jetstream.RoomID, roomID)
|
||||
m.Header.Set("typing", strconv.FormatBool(typing))
|
||||
m.Header.Set("timeout_ms", strconv.Itoa(int(timeoutMS)))
|
||||
_, err := p.JetStream.PublishMsg(m, nats.Context(ctx))
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *SyncAPIProducer) SendPresence(
|
||||
ctx context.Context, userID string, presence types.Presence, statusMsg *string, lastActiveAgo int64,
|
||||
) error {
|
||||
m := nats.NewMsg(p.TopicPresenceEvent)
|
||||
m.Header.Set(jetstream.UserID, userID)
|
||||
m.Header.Set("presence", presence.String())
|
||||
if statusMsg != nil {
|
||||
m.Header.Set("status_msg", *statusMsg)
|
||||
}
|
||||
lastActiveTS := gomatrixserverlib.AsTimestamp(time.Now().Add(-(time.Duration(lastActiveAgo) * time.Millisecond)))
|
||||
|
||||
m.Header.Set("last_active_ts", strconv.Itoa(int(lastActiveTS)))
|
||||
log.Debugf("Sending presence to syncAPI: %+v", m.Header)
|
||||
_, err := p.JetStream.PublishMsg(m, nats.Context(ctx))
|
||||
return err
|
||||
}
|
||||
|
|
@ -104,28 +104,31 @@ func NewOutgoingQueues(
|
|||
}
|
||||
// Look up which servers we have pending items for and then rehydrate those queues.
|
||||
if !disabled {
|
||||
time.AfterFunc(time.Second*5, func() {
|
||||
serverNames := map[gomatrixserverlib.ServerName]struct{}{}
|
||||
if names, err := db.GetPendingPDUServerNames(context.Background()); err == nil {
|
||||
for _, serverName := range names {
|
||||
serverNames[serverName] = struct{}{}
|
||||
}
|
||||
} else {
|
||||
log.WithError(err).Error("Failed to get PDU server names for destination queue hydration")
|
||||
serverNames := map[gomatrixserverlib.ServerName]struct{}{}
|
||||
if names, err := db.GetPendingPDUServerNames(context.Background()); err == nil {
|
||||
for _, serverName := range names {
|
||||
serverNames[serverName] = struct{}{}
|
||||
}
|
||||
if names, err := db.GetPendingEDUServerNames(context.Background()); err == nil {
|
||||
for _, serverName := range names {
|
||||
serverNames[serverName] = struct{}{}
|
||||
}
|
||||
} else {
|
||||
log.WithError(err).Error("Failed to get EDU server names for destination queue hydration")
|
||||
} else {
|
||||
log.WithError(err).Error("Failed to get PDU server names for destination queue hydration")
|
||||
}
|
||||
if names, err := db.GetPendingEDUServerNames(context.Background()); err == nil {
|
||||
for _, serverName := range names {
|
||||
serverNames[serverName] = struct{}{}
|
||||
}
|
||||
for serverName := range serverNames {
|
||||
if queue := queues.getQueue(serverName); queue != nil {
|
||||
queue.wakeQueueIfNeeded()
|
||||
}
|
||||
} else {
|
||||
log.WithError(err).Error("Failed to get EDU server names for destination queue hydration")
|
||||
}
|
||||
offset, step := time.Second*5, time.Second
|
||||
if max := len(serverNames); max > 120 {
|
||||
step = (time.Second * 120) / time.Duration(max)
|
||||
}
|
||||
for serverName := range serverNames {
|
||||
if queue := queues.getQueue(serverName); queue != nil {
|
||||
time.AfterFunc(offset, queue.wakeQueueIfNeeded)
|
||||
offset += step
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return queues
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ import (
|
|||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
eduserverAPI "github.com/matrix-org/dendrite/eduserver/api"
|
||||
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
||||
"github.com/matrix-org/dendrite/federationapi/producers"
|
||||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/internal/httputil"
|
||||
keyserverAPI "github.com/matrix-org/dendrite/keyserver/api"
|
||||
|
|
@ -29,6 +29,7 @@ import (
|
|||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
|
@ -44,7 +45,6 @@ func Setup(
|
|||
fedMux, keyMux, wkMux *mux.Router,
|
||||
cfg *config.FederationAPI,
|
||||
rsAPI roomserverAPI.RoomserverInternalAPI,
|
||||
eduAPI eduserverAPI.EDUServerInputAPI,
|
||||
fsAPI federationAPI.FederationInternalAPI,
|
||||
keys gomatrixserverlib.JSONVerifier,
|
||||
federation *gomatrixserverlib.FederationClient,
|
||||
|
|
@ -52,7 +52,12 @@ func Setup(
|
|||
keyAPI keyserverAPI.KeyInternalAPI,
|
||||
mscCfg *config.MSCs,
|
||||
servers federationAPI.ServersInRoomProvider,
|
||||
producer *producers.SyncAPIProducer,
|
||||
) {
|
||||
prometheus.MustRegister(
|
||||
pduCountTotal, eduCountTotal,
|
||||
)
|
||||
|
||||
v2keysmux := keyMux.PathPrefix("/v2").Subrouter()
|
||||
v1fedmux := fedMux.PathPrefix("/v1").Subrouter()
|
||||
v2fedmux := fedMux.PathPrefix("/v2").Subrouter()
|
||||
|
|
@ -116,7 +121,7 @@ func Setup(
|
|||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||
return Send(
|
||||
httpReq, request, gomatrixserverlib.TransactionID(vars["txnID"]),
|
||||
cfg, rsAPI, eduAPI, keyAPI, keys, federation, mu, servers,
|
||||
cfg, rsAPI, keyAPI, keys, federation, mu, servers, producer,
|
||||
)
|
||||
},
|
||||
)).Methods(http.MethodPut, http.MethodOptions)
|
||||
|
|
|
|||
|
|
@ -23,12 +23,14 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
eduserverAPI "github.com/matrix-org/dendrite/eduserver/api"
|
||||
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
||||
"github.com/matrix-org/dendrite/federationapi/producers"
|
||||
"github.com/matrix-org/dendrite/federationapi/types"
|
||||
"github.com/matrix-org/dendrite/internal"
|
||||
keyapi "github.com/matrix-org/dendrite/keyserver/api"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
syncTypes "github.com/matrix-org/dendrite/syncapi/types"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
|
@ -72,12 +74,6 @@ var (
|
|||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(
|
||||
pduCountTotal, eduCountTotal,
|
||||
)
|
||||
}
|
||||
|
||||
var inFlightTxnsPerOrigin sync.Map // transaction ID -> chan util.JSONResponse
|
||||
|
||||
// Send implements /_matrix/federation/v1/send/{txnID}
|
||||
|
|
@ -87,12 +83,12 @@ func Send(
|
|||
txnID gomatrixserverlib.TransactionID,
|
||||
cfg *config.FederationAPI,
|
||||
rsAPI api.RoomserverInternalAPI,
|
||||
eduAPI eduserverAPI.EDUServerInputAPI,
|
||||
keyAPI keyapi.KeyInternalAPI,
|
||||
keys gomatrixserverlib.JSONVerifier,
|
||||
federation *gomatrixserverlib.FederationClient,
|
||||
mu *internal.MutexByRoom,
|
||||
servers federationAPI.ServersInRoomProvider,
|
||||
producer *producers.SyncAPIProducer,
|
||||
) util.JSONResponse {
|
||||
// First we should check if this origin has already submitted this
|
||||
// txn ID to us. If they have and the txnIDs map contains an entry,
|
||||
|
|
@ -126,13 +122,14 @@ func Send(
|
|||
defer inFlightTxnsPerOrigin.Delete(index)
|
||||
|
||||
t := txnReq{
|
||||
rsAPI: rsAPI,
|
||||
eduAPI: eduAPI,
|
||||
keys: keys,
|
||||
federation: federation,
|
||||
servers: servers,
|
||||
keyAPI: keyAPI,
|
||||
roomsMu: mu,
|
||||
rsAPI: rsAPI,
|
||||
keys: keys,
|
||||
federation: federation,
|
||||
servers: servers,
|
||||
keyAPI: keyAPI,
|
||||
roomsMu: mu,
|
||||
producer: producer,
|
||||
inboundPresenceEnabled: cfg.Matrix.Presence.EnableInbound,
|
||||
}
|
||||
|
||||
var txnEvents struct {
|
||||
|
|
@ -184,13 +181,14 @@ func Send(
|
|||
|
||||
type txnReq struct {
|
||||
gomatrixserverlib.Transaction
|
||||
rsAPI api.RoomserverInternalAPI
|
||||
eduAPI eduserverAPI.EDUServerInputAPI
|
||||
keyAPI keyapi.KeyInternalAPI
|
||||
keys gomatrixserverlib.JSONVerifier
|
||||
federation txnFederationClient
|
||||
roomsMu *internal.MutexByRoom
|
||||
servers federationAPI.ServersInRoomProvider
|
||||
rsAPI api.RoomserverInternalAPI
|
||||
keyAPI keyapi.KeyInternalAPI
|
||||
keys gomatrixserverlib.JSONVerifier
|
||||
federation txnFederationClient
|
||||
roomsMu *internal.MutexByRoom
|
||||
servers federationAPI.ServersInRoomProvider
|
||||
producer *producers.SyncAPIProducer
|
||||
inboundPresenceEnabled bool
|
||||
}
|
||||
|
||||
// A subset of FederationClient functionality that txn requires. Useful for testing.
|
||||
|
|
@ -329,8 +327,8 @@ func (t *txnReq) processEDUs(ctx context.Context) {
|
|||
util.GetLogger(ctx).Debugf("Dropping typing event where sender domain (%q) doesn't match origin (%q)", domain, t.Origin)
|
||||
continue
|
||||
}
|
||||
if err := eduserverAPI.SendTyping(ctx, t.eduAPI, typingPayload.UserID, typingPayload.RoomID, typingPayload.Typing, 30*1000); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("Failed to send typing event to edu server")
|
||||
if err := t.producer.SendTyping(ctx, typingPayload.UserID, typingPayload.RoomID, typingPayload.Typing, 30*1000); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("Failed to send typing event to JetStream")
|
||||
}
|
||||
case gomatrixserverlib.MDirectToDevice:
|
||||
// https://matrix.org/docs/spec/server_server/r0.1.3#m-direct-to-device-schema
|
||||
|
|
@ -342,12 +340,12 @@ func (t *txnReq) processEDUs(ctx context.Context) {
|
|||
for userID, byUser := range directPayload.Messages {
|
||||
for deviceID, message := range byUser {
|
||||
// TODO: check that the user and the device actually exist here
|
||||
if err := eduserverAPI.SendToDevice(ctx, t.eduAPI, directPayload.Sender, userID, deviceID, directPayload.Type, message); err != nil {
|
||||
if err := t.producer.SendToDevice(ctx, directPayload.Sender, userID, deviceID, directPayload.Type, message); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).WithFields(logrus.Fields{
|
||||
"sender": directPayload.Sender,
|
||||
"user_id": userID,
|
||||
"device_id": deviceID,
|
||||
}).Error("Failed to send send-to-device event to edu server")
|
||||
}).Error("Failed to send send-to-device event to JetStream")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -355,7 +353,7 @@ func (t *txnReq) processEDUs(ctx context.Context) {
|
|||
t.processDeviceListUpdate(ctx, e)
|
||||
case gomatrixserverlib.MReceipt:
|
||||
// https://matrix.org/docs/spec/server_server/r0.1.4#receipts
|
||||
payload := map[string]eduserverAPI.FederationReceiptMRead{}
|
||||
payload := map[string]types.FederationReceiptMRead{}
|
||||
|
||||
if err := json.Unmarshal(e.Content, &payload); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Debug("Failed to unmarshal receipt event")
|
||||
|
|
@ -379,23 +377,47 @@ func (t *txnReq) processEDUs(ctx context.Context) {
|
|||
"user_id": userID,
|
||||
"room_id": roomID,
|
||||
"events": mread.EventIDs,
|
||||
}).Error("Failed to send receipt event to edu server")
|
||||
}).Error("Failed to send receipt event to JetStream")
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
case eduserverAPI.MSigningKeyUpdate:
|
||||
case types.MSigningKeyUpdate:
|
||||
if err := t.processSigningKeyUpdate(ctx, e); err != nil {
|
||||
logrus.WithError(err).Errorf("Failed to process signing key update")
|
||||
}
|
||||
case gomatrixserverlib.MPresence:
|
||||
if t.inboundPresenceEnabled {
|
||||
if err := t.processPresence(ctx, e); err != nil {
|
||||
logrus.WithError(err).Errorf("Failed to process presence update")
|
||||
}
|
||||
}
|
||||
default:
|
||||
util.GetLogger(ctx).WithField("type", e.Type).Debug("Unhandled EDU")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// processPresence handles m.receipt events
|
||||
func (t *txnReq) processPresence(ctx context.Context, e gomatrixserverlib.EDU) error {
|
||||
payload := types.Presence{}
|
||||
if err := json.Unmarshal(e.Content, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, content := range payload.Push {
|
||||
presence, ok := syncTypes.PresenceFromString(content.Presence)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if err := t.producer.SendPresence(ctx, content.UserID, presence, content.StatusMsg, content.LastActiveAgo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *txnReq) processSigningKeyUpdate(ctx context.Context, e gomatrixserverlib.EDU) error {
|
||||
var updatePayload eduserverAPI.CrossSigningKeyUpdate
|
||||
var updatePayload keyapi.CrossSigningKeyUpdate
|
||||
if err := json.Unmarshal(e.Content, &updatePayload); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).WithFields(logrus.Fields{
|
||||
"user_id": updatePayload.UserID,
|
||||
|
|
@ -422,7 +444,7 @@ func (t *txnReq) processSigningKeyUpdate(ctx context.Context, e gomatrixserverli
|
|||
return nil
|
||||
}
|
||||
|
||||
// processReceiptEvent sends receipt events to the edu server
|
||||
// processReceiptEvent sends receipt events to JetStream
|
||||
func (t *txnReq) processReceiptEvent(ctx context.Context,
|
||||
userID, roomID, receiptType string,
|
||||
timestamp gomatrixserverlib.Timestamp,
|
||||
|
|
@ -430,17 +452,7 @@ func (t *txnReq) processReceiptEvent(ctx context.Context,
|
|||
) error {
|
||||
// store every event
|
||||
for _, eventID := range eventIDs {
|
||||
req := eduserverAPI.InputReceiptEventRequest{
|
||||
InputReceiptEvent: eduserverAPI.InputReceiptEvent{
|
||||
UserID: userID,
|
||||
RoomID: roomID,
|
||||
EventID: eventID,
|
||||
Type: receiptType,
|
||||
Timestamp: timestamp,
|
||||
},
|
||||
}
|
||||
resp := eduserverAPI.InputReceiptEventResponse{}
|
||||
if err := t.eduAPI.InputReceiptEvent(ctx, &req, &resp); err != nil {
|
||||
if err := t.producer.SendReceipt(ctx, userID, roomID, eventID, receiptType, timestamp); err != nil {
|
||||
return fmt.Errorf("unable to set receipt event: %w", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
eduAPI "github.com/matrix-org/dendrite/eduserver/api"
|
||||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/internal/test"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
|
|
@ -53,44 +52,6 @@ func init() {
|
|||
}
|
||||
}
|
||||
|
||||
type testEDUProducer struct {
|
||||
// this producer keeps track of calls to InputTypingEvent
|
||||
invocations []eduAPI.InputTypingEventRequest
|
||||
}
|
||||
|
||||
func (p *testEDUProducer) InputTypingEvent(
|
||||
ctx context.Context,
|
||||
request *eduAPI.InputTypingEventRequest,
|
||||
response *eduAPI.InputTypingEventResponse,
|
||||
) error {
|
||||
p.invocations = append(p.invocations, *request)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *testEDUProducer) InputSendToDeviceEvent(
|
||||
ctx context.Context,
|
||||
request *eduAPI.InputSendToDeviceEventRequest,
|
||||
response *eduAPI.InputSendToDeviceEventResponse,
|
||||
) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *testEDUProducer) InputReceiptEvent(
|
||||
ctx context.Context,
|
||||
request *eduAPI.InputReceiptEventRequest,
|
||||
response *eduAPI.InputReceiptEventResponse,
|
||||
) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *testEDUProducer) InputCrossSigningKeyUpdate(
|
||||
ctx context.Context,
|
||||
request *eduAPI.InputCrossSigningKeyUpdateRequest,
|
||||
response *eduAPI.InputCrossSigningKeyUpdateResponse,
|
||||
) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type testRoomserverAPI struct {
|
||||
api.RoomserverInternalAPITrace
|
||||
inputRoomEvents []api.InputRoomEvent
|
||||
|
|
@ -225,7 +186,6 @@ func (c *txnFedClient) LookupMissingEvents(ctx context.Context, s gomatrixserver
|
|||
func mustCreateTransaction(rsAPI api.RoomserverInternalAPI, fedClient txnFederationClient, pdus []json.RawMessage) *txnReq {
|
||||
t := &txnReq{
|
||||
rsAPI: rsAPI,
|
||||
eduAPI: &testEDUProducer{},
|
||||
keys: &test.NopJSONVerifier{},
|
||||
federation: fedClient,
|
||||
roomsMu: internal.NewMutexByRoom(),
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import (
|
|||
"github.com/matrix-org/gomatrixserverlib"
|
||||
)
|
||||
|
||||
const MSigningKeyUpdate = "m.signing_key_update" // TODO: move to gomatrixserverlib
|
||||
|
||||
// A JoinedHost is a server that is joined to a matrix room.
|
||||
type JoinedHost struct {
|
||||
// The MemberEventID of a m.room.member join event.
|
||||
|
|
@ -51,3 +53,28 @@ type InboundPeek struct {
|
|||
RenewedTimestamp int64
|
||||
RenewalInterval int64
|
||||
}
|
||||
|
||||
type FederationReceiptMRead struct {
|
||||
User map[string]FederationReceiptData `json:"m.read"`
|
||||
}
|
||||
|
||||
type FederationReceiptData struct {
|
||||
Data ReceiptTS `json:"data"`
|
||||
EventIDs []string `json:"event_ids"`
|
||||
}
|
||||
|
||||
type ReceiptTS struct {
|
||||
TS gomatrixserverlib.Timestamp `json:"ts"`
|
||||
}
|
||||
|
||||
type Presence struct {
|
||||
Push []PresenceContent `json:"push"`
|
||||
}
|
||||
|
||||
type PresenceContent struct {
|
||||
CurrentlyActive bool `json:"currently_active,omitempty"`
|
||||
LastActiveAgo int64 `json:"last_active_ago"`
|
||||
Presence string `json:"presence"`
|
||||
StatusMsg *string `json:"status_msg,omitempty"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
|
|
|||
38
go.mod
38
go.mod
|
|
@ -12,20 +12,20 @@ require (
|
|||
github.com/MFAshby/stdemuxerhook v1.0.0
|
||||
github.com/Masterminds/semver/v3 v3.1.1
|
||||
github.com/codeclysm/extract v2.2.0+incompatible
|
||||
github.com/containerd/containerd v1.5.9 // indirect
|
||||
github.com/docker/docker v20.10.12+incompatible
|
||||
github.com/containerd/containerd v1.6.2 // indirect
|
||||
github.com/docker/docker v20.10.14+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/frankban/quicktest v1.14.0 // indirect
|
||||
github.com/getsentry/sentry-go v0.12.0
|
||||
github.com/frankban/quicktest v1.14.3 // indirect
|
||||
github.com/getsentry/sentry-go v0.13.0
|
||||
github.com/gologme/log v1.3.0
|
||||
github.com/google/go-cmp v0.5.6
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/google/go-cmp v0.5.7
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/h2non/filetype v1.1.3 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/juju/testing v0.0.0-20211215003918-77eb13d6cad2 // indirect
|
||||
github.com/lib/pq v1.10.4
|
||||
github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494 // indirect
|
||||
github.com/lib/pq v1.10.5
|
||||
github.com/libp2p/go-libp2p v0.13.0
|
||||
github.com/libp2p/go-libp2p-circuit v0.4.0
|
||||
github.com/libp2p/go-libp2p-core v0.8.3
|
||||
|
|
@ -38,18 +38,16 @@ require (
|
|||
github.com/matrix-org/go-http-js-libp2p v0.0.0-20200518170932-783164aeeda4
|
||||
github.com/matrix-org/go-sqlite3-js v0.0.0-20210709140738-b0d1ba599a6d
|
||||
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16
|
||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20220317164600-0980b7f341e0
|
||||
github.com/matrix-org/pinecone v0.0.0-20220323142759-6fb077377278
|
||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20220405134050-301e340659d5
|
||||
github.com/matrix-org/pinecone v0.0.0-20220408153826-2999ea29ed48
|
||||
github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4
|
||||
github.com/mattn/go-sqlite3 v1.14.10
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/nats-io/nats-server/v2 v2.7.4-0.20220309205833-773636c1c5bb
|
||||
github.com/nats-io/nats.go v1.13.1-0.20220308171302-2f2f6968e98d
|
||||
github.com/neilalexander/utp v0.1.1-0.20210727203401-54ae7b1cd5f9
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||
github.com/ngrok/sqlmw v0.0.0-20211220175533-9d16fdc47b31
|
||||
github.com/onsi/ginkgo v1.16.4 // indirect
|
||||
github.com/onsi/gomega v1.13.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/errors v0.9.1
|
||||
|
|
@ -62,15 +60,13 @@ require (
|
|||
github.com/uber/jaeger-lib v2.4.1+incompatible
|
||||
github.com/yggdrasil-network/yggdrasil-go v0.4.3
|
||||
go.uber.org/atomic v1.9.0
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
|
||||
golang.org/x/mobile v0.0.0-20220112015953-858099ff7816
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
|
||||
golang.org/x/image v0.0.0-20220321031419-a8550c1d254a
|
||||
golang.org/x/mobile v0.0.0-20220407111146-e579adbbc4a2
|
||||
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
gopkg.in/h2non/bimg.v1 v1.1.5
|
||||
gopkg.in/h2non/bimg.v1 v1.1.9
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
nhooyr.io/websocket v1.8.7
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
package caching
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
|
@ -53,8 +53,8 @@ func (t *EDUCache) newRoomData() *roomData {
|
|||
}
|
||||
}
|
||||
|
||||
// New returns a new EDUCache initialised for use.
|
||||
func New() *EDUCache {
|
||||
// NewTypingCache returns a new EDUCache initialised for use.
|
||||
func NewTypingCache() *EDUCache {
|
||||
return &EDUCache{data: make(map[string]*roomData)}
|
||||
}
|
||||
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
package caching
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
|
@ -24,9 +24,9 @@ import (
|
|||
)
|
||||
|
||||
func TestEDUCache(t *testing.T) {
|
||||
tCache := New()
|
||||
tCache := NewTypingCache()
|
||||
if tCache == nil {
|
||||
t.Fatal("New failed")
|
||||
t.Fatal("NewTypingCache failed")
|
||||
}
|
||||
|
||||
t.Run("AddTypingUser", func(t *testing.T) {
|
||||
|
|
@ -17,6 +17,8 @@ package eventutil
|
|||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"github.com/matrix-org/dendrite/syncapi/types"
|
||||
)
|
||||
|
||||
// ErrProfileNoExists is returned when trying to lookup a user's profile that
|
||||
|
|
@ -26,9 +28,10 @@ var ErrProfileNoExists = errors.New("no known profile for given user ID")
|
|||
// AccountData represents account data sent from the client API server to the
|
||||
// sync API server
|
||||
type AccountData struct {
|
||||
RoomID string `json:"room_id"`
|
||||
Type string `json:"type"`
|
||||
ReadMarker *ReadMarkerJSON `json:"read_marker,omitempty"` // optional
|
||||
RoomID string `json:"room_id"`
|
||||
Type string `json:"type"`
|
||||
ReadMarker *ReadMarkerJSON `json:"read_marker,omitempty"` // optional
|
||||
IgnoredUsers *types.IgnoredUsers `json:"ignored_users,omitempty"` // optional
|
||||
}
|
||||
|
||||
type ReadMarkerJSON struct {
|
||||
|
|
|
|||
|
|
@ -169,8 +169,9 @@ func MakeHTMLAPI(metricsName string, f func(http.ResponseWriter, *http.Request)
|
|||
return promhttp.InstrumentHandlerCounter(
|
||||
promauto.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: metricsName,
|
||||
Help: "Total number of http requests for HTML resources",
|
||||
Name: metricsName,
|
||||
Help: "Total number of http requests for HTML resources",
|
||||
Namespace: "dendrite",
|
||||
},
|
||||
[]string{"code"},
|
||||
),
|
||||
|
|
@ -201,7 +202,28 @@ func MakeInternalAPI(metricsName string, f func(*http.Request) util.JSONResponse
|
|||
h.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(withSpan)
|
||||
return promhttp.InstrumentHandlerCounter(
|
||||
promauto.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: metricsName + "_requests_total",
|
||||
Help: "Total number of internal API calls",
|
||||
Namespace: "dendrite",
|
||||
},
|
||||
[]string{"code"},
|
||||
),
|
||||
promhttp.InstrumentHandlerResponseSize(
|
||||
promauto.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: "dendrite",
|
||||
Name: metricsName + "_response_size_bytes",
|
||||
Help: "A histogram of response sizes for requests.",
|
||||
Buckets: []float64{200, 500, 900, 1500, 5000, 15000, 50000, 100000},
|
||||
},
|
||||
[]string{},
|
||||
),
|
||||
http.HandlerFunc(withSpan),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// MakeFedAPI makes an http.Handler that checks matrix federation authentication.
|
||||
|
|
|
|||
|
|
@ -78,8 +78,6 @@ func MakeConfig(configDir, kafkaURI, database, host string, startPort int) (*con
|
|||
cfg.Global.ServerName = gomatrixserverlib.ServerName(assignAddress())
|
||||
cfg.Global.PrivateKeyPath = config.Path(serverKeyPath)
|
||||
|
||||
cfg.FederationAPI.FederationCertificatePaths = []config.Path{config.Path(tlsCertPath)}
|
||||
|
||||
cfg.MediaAPI.BasePath = config.Path(mediaBasePath)
|
||||
|
||||
cfg.Global.JetStream.Addresses = []string{kafkaURI}
|
||||
|
|
@ -97,7 +95,6 @@ func MakeConfig(configDir, kafkaURI, database, host string, startPort int) (*con
|
|||
cfg.UserAPI.AccountDatabase.ConnectionString = config.DataSource(database)
|
||||
|
||||
cfg.AppServiceAPI.InternalAPI.Listen = assignAddress()
|
||||
cfg.EDUServer.InternalAPI.Listen = assignAddress()
|
||||
cfg.FederationAPI.InternalAPI.Listen = assignAddress()
|
||||
cfg.KeyServer.InternalAPI.Listen = assignAddress()
|
||||
cfg.MediaAPI.InternalAPI.Listen = assignAddress()
|
||||
|
|
@ -106,7 +103,6 @@ func MakeConfig(configDir, kafkaURI, database, host string, startPort int) (*con
|
|||
cfg.UserAPI.InternalAPI.Listen = assignAddress()
|
||||
|
||||
cfg.AppServiceAPI.InternalAPI.Connect = cfg.AppServiceAPI.InternalAPI.Listen
|
||||
cfg.EDUServer.InternalAPI.Connect = cfg.EDUServer.InternalAPI.Listen
|
||||
cfg.FederationAPI.InternalAPI.Connect = cfg.FederationAPI.InternalAPI.Listen
|
||||
cfg.KeyServer.InternalAPI.Connect = cfg.KeyServer.InternalAPI.Listen
|
||||
cfg.MediaAPI.InternalAPI.Connect = cfg.MediaAPI.InternalAPI.Listen
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ var build string
|
|||
|
||||
const (
|
||||
VersionMajor = 0
|
||||
VersionMinor = 7
|
||||
VersionPatch = 0
|
||||
VersionMinor = 8
|
||||
VersionPatch = 1
|
||||
VersionTag = "" // example: "rc1"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
eduapi "github.com/matrix-org/dendrite/eduserver/api"
|
||||
"github.com/matrix-org/dendrite/keyserver/types"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
|
|
@ -66,14 +65,25 @@ const (
|
|||
|
||||
// DeviceMessage represents the message produced into Kafka by the key server.
|
||||
type DeviceMessage struct {
|
||||
Type DeviceMessageType `json:"Type,omitempty"`
|
||||
*DeviceKeys `json:"DeviceKeys,omitempty"`
|
||||
*eduapi.OutputCrossSigningKeyUpdate `json:"CrossSigningKeyUpdate,omitempty"`
|
||||
Type DeviceMessageType `json:"Type,omitempty"`
|
||||
*DeviceKeys `json:"DeviceKeys,omitempty"`
|
||||
*OutputCrossSigningKeyUpdate `json:"CrossSigningKeyUpdate,omitempty"`
|
||||
// A monotonically increasing number which represents device changes for this user.
|
||||
StreamID int64
|
||||
DeviceChangeID int64
|
||||
}
|
||||
|
||||
// OutputCrossSigningKeyUpdate is an entry in the signing key update output kafka log
|
||||
type OutputCrossSigningKeyUpdate struct {
|
||||
CrossSigningKeyUpdate `json:"signing_keys"`
|
||||
}
|
||||
|
||||
type CrossSigningKeyUpdate struct {
|
||||
MasterKey *gomatrixserverlib.CrossSigningKey `json:"master_key,omitempty"`
|
||||
SelfSigningKey *gomatrixserverlib.CrossSigningKey `json:"self_signing_key,omitempty"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
// DeviceKeysEqual returns true if the device keys updates contain the
|
||||
// same display name and key JSON. This will return false if either of
|
||||
// the updates is not a device keys update, or if the user ID/device ID
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
eduserverAPI "github.com/matrix-org/dendrite/eduserver/api"
|
||||
"github.com/matrix-org/dendrite/keyserver/api"
|
||||
"github.com/matrix-org/dendrite/keyserver/types"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
|
|
@ -246,7 +245,7 @@ func (a *KeyInternalAPI) PerformUploadDeviceKeys(ctx context.Context, req *api.P
|
|||
}
|
||||
|
||||
// Finally, generate a notification that we updated the keys.
|
||||
update := eduserverAPI.CrossSigningKeyUpdate{
|
||||
update := api.CrossSigningKeyUpdate{
|
||||
UserID: req.UserID,
|
||||
}
|
||||
if mk, ok := byPurpose[gomatrixserverlib.CrossSigningKeyPurposeMaster]; ok {
|
||||
|
|
@ -337,7 +336,7 @@ func (a *KeyInternalAPI) PerformUploadDeviceSignatures(ctx context.Context, req
|
|||
for userID := range req.Signatures {
|
||||
masterKey := queryRes.MasterKeys[userID]
|
||||
selfSigningKey := queryRes.SelfSigningKeys[userID]
|
||||
update := eduserverAPI.CrossSigningKeyUpdate{
|
||||
update := api.CrossSigningKeyUpdate{
|
||||
UserID: userID,
|
||||
MasterKey: &masterKey,
|
||||
SelfSigningKey: &selfSigningKey,
|
||||
|
|
|
|||
|
|
@ -157,8 +157,15 @@ func (u *DeviceListUpdater) Start() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset, step := time.Second*10, time.Second
|
||||
if max := len(staleLists); max > 120 {
|
||||
step = (time.Second * 120) / time.Duration(max)
|
||||
}
|
||||
for _, userID := range staleLists {
|
||||
u.notifyWorkers(userID)
|
||||
time.AfterFunc(offset, func() {
|
||||
u.notifyWorkers(userID)
|
||||
})
|
||||
offset += step
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
|
||||
eduapi "github.com/matrix-org/dendrite/eduserver/api"
|
||||
"github.com/matrix-org/dendrite/keyserver/api"
|
||||
"github.com/matrix-org/dendrite/keyserver/storage"
|
||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||
|
|
@ -70,10 +69,10 @@ func (p *KeyChange) ProduceKeyChanges(keys []api.DeviceMessage) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *KeyChange) ProduceSigningKeyUpdate(key eduapi.CrossSigningKeyUpdate) error {
|
||||
func (p *KeyChange) ProduceSigningKeyUpdate(key api.CrossSigningKeyUpdate) error {
|
||||
output := &api.DeviceMessage{
|
||||
Type: api.TypeCrossSigningUpdate,
|
||||
OutputCrossSigningKeyUpdate: &eduapi.OutputCrossSigningKeyUpdate{
|
||||
OutputCrossSigningKeyUpdate: &api.OutputCrossSigningKeyUpdate{
|
||||
CrossSigningKeyUpdate: key,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ func (r *uploadRequest) doUpload(
|
|||
}
|
||||
|
||||
// Check if temp file size exceeds max file size configuration
|
||||
if bytesWritten > types.FileSizeBytes(*cfg.MaxFileSizeBytes) {
|
||||
if *cfg.MaxFileSizeBytes > 0 && bytesWritten > types.FileSizeBytes(*cfg.MaxFileSizeBytes) {
|
||||
fileutils.RemoveDir(tmpDir, r.Logger) // delete temp file
|
||||
return requestEntityTooLargeJSONResponse(*cfg.MaxFileSizeBytes)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ func Test_uploadRequest_doUpload(t *testing.T) {
|
|||
}
|
||||
|
||||
maxSize := config.FileSizeBytes(8)
|
||||
unlimitedSize := config.FileSizeBytes(0)
|
||||
logger := log.New().WithField("mediaapi", "test")
|
||||
testdataPath := filepath.Join(wd, "./testdata")
|
||||
|
||||
|
|
@ -117,6 +118,27 @@ func Test_uploadRequest_doUpload(t *testing.T) {
|
|||
},
|
||||
want: requestEntityTooLargeJSONResponse(maxSize),
|
||||
},
|
||||
{
|
||||
name: "upload ok with unlimited filesize",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
reqReader: strings.NewReader("test test test"),
|
||||
cfg: &config.MediaAPI{
|
||||
MaxFileSizeBytes: &unlimitedSize,
|
||||
BasePath: config.Path(testdataPath),
|
||||
AbsBasePath: config.Path(testdataPath),
|
||||
DynamicThumbnails: false,
|
||||
},
|
||||
db: db,
|
||||
},
|
||||
fields: fields{
|
||||
Logger: logger,
|
||||
MediaMetadata: &types.MediaMetadata{
|
||||
MediaID: "1339",
|
||||
UploadName: "test fail",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -170,6 +170,9 @@ type RoomserverInternalAPI interface {
|
|||
// PerformForget forgets a rooms history for a specific user
|
||||
PerformForget(ctx context.Context, req *PerformForgetRequest, resp *PerformForgetResponse) error
|
||||
|
||||
// PerformRoomUpgrade upgrades a room to a newer version
|
||||
PerformRoomUpgrade(ctx context.Context, req *PerformRoomUpgradeRequest, resp *PerformRoomUpgradeResponse)
|
||||
|
||||
// Asks for the default room version as preferred by the server.
|
||||
QueryRoomVersionCapabilities(
|
||||
ctx context.Context,
|
||||
|
|
|
|||
|
|
@ -67,6 +67,15 @@ func (t *RoomserverInternalAPITrace) PerformUnpeek(
|
|||
util.GetLogger(ctx).Infof("PerformUnpeek req=%+v res=%+v", js(req), js(res))
|
||||
}
|
||||
|
||||
func (t *RoomserverInternalAPITrace) PerformRoomUpgrade(
|
||||
ctx context.Context,
|
||||
req *PerformRoomUpgradeRequest,
|
||||
res *PerformRoomUpgradeResponse,
|
||||
) {
|
||||
t.Impl.PerformRoomUpgrade(ctx, req, res)
|
||||
util.GetLogger(ctx).Infof("PerformRoomUpgrade req=%+v res=%+v", js(req), js(res))
|
||||
}
|
||||
|
||||
func (t *RoomserverInternalAPITrace) PerformJoin(
|
||||
ctx context.Context,
|
||||
req *PerformJoinRequest,
|
||||
|
|
|
|||
|
|
@ -203,3 +203,14 @@ type PerformForgetRequest struct {
|
|||
}
|
||||
|
||||
type PerformForgetResponse struct{}
|
||||
|
||||
type PerformRoomUpgradeRequest struct {
|
||||
RoomID string `json:"room_id"`
|
||||
UserID string `json:"user_id"`
|
||||
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
|
||||
}
|
||||
|
||||
type PerformRoomUpgradeResponse struct {
|
||||
NewRoomID string
|
||||
Error *PerformError
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,6 +128,8 @@ type QueryMembershipForUserResponse struct {
|
|||
type QueryMembershipsForRoomRequest struct {
|
||||
// If true, only returns the membership events of "join" membership
|
||||
JoinedOnly bool `json:"joined_only"`
|
||||
// If true, only returns the membership events of local users
|
||||
LocalOnly bool `json:"local_only"`
|
||||
// ID of the room to fetch memberships from
|
||||
RoomID string `json:"room_id"`
|
||||
// Optional - ID of the user sending the request, for checking if the
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ type RoomserverInternalAPI struct {
|
|||
*perform.Publisher
|
||||
*perform.Backfiller
|
||||
*perform.Forgetter
|
||||
*perform.Upgrader
|
||||
ProcessContext *process.ProcessContext
|
||||
DB storage.Database
|
||||
Cfg *config.RoomServer
|
||||
|
|
@ -159,6 +160,10 @@ func (r *RoomserverInternalAPI) SetFederationAPI(fsAPI fsAPI.FederationInternalA
|
|||
r.Forgetter = &perform.Forgetter{
|
||||
DB: r.DB,
|
||||
}
|
||||
r.Upgrader = &perform.Upgrader{
|
||||
Cfg: r.Cfg,
|
||||
URSAPI: r,
|
||||
}
|
||||
|
||||
if err := r.Inputer.Start(); err != nil {
|
||||
logrus.WithError(err).Panic("failed to start roomserver input API")
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ func (r *Inputer) startWorkerForRoom(roomID string) {
|
|||
// will look to see if we have a worker for that room which has its
|
||||
// own consumer. If we don't, we'll start one.
|
||||
func (r *Inputer) Start() error {
|
||||
prometheus.MustRegister(roomserverInputBackpressure, processRoomEventDuration)
|
||||
_, err := r.JetStream.Subscribe(
|
||||
"", // This is blank because we specified it in BindStream.
|
||||
func(m *nats.Msg) {
|
||||
|
|
@ -421,10 +422,6 @@ func (r *Inputer) WriteOutputEvents(roomID string, updates []api.OutputEvent) er
|
|||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(roomserverInputBackpressure)
|
||||
}
|
||||
|
||||
var roomserverInputBackpressure = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: "dendrite",
|
||||
|
|
|
|||
|
|
@ -37,10 +37,6 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(processRoomEventDuration)
|
||||
}
|
||||
|
||||
// TODO: Does this value make sense?
|
||||
const MaximumMissingProcessingTime = time.Minute * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -613,12 +613,13 @@ func (t *missingStateReq) lookupMissingStateViaState(
|
|||
return nil, err
|
||||
}
|
||||
// Check that the returned state is valid.
|
||||
if err := state.Check(ctx, roomVersion, t.keys, nil); err != nil {
|
||||
authEvents, stateEvents, err := state.Check(ctx, roomVersion, t.keys, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsedState := &parsedRespState{
|
||||
AuthEvents: make([]*gomatrixserverlib.Event, len(state.AuthEvents)),
|
||||
StateEvents: make([]*gomatrixserverlib.Event, len(state.StateEvents)),
|
||||
AuthEvents: authEvents,
|
||||
StateEvents: stateEvents,
|
||||
}
|
||||
// Cache the results of this state lookup and deduplicate anything we already
|
||||
// have in the cache, freeing up memory.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package input_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -12,30 +11,22 @@ import (
|
|||
"github.com/matrix-org/dendrite/roomserver/internal/input"
|
||||
"github.com/matrix-org/dendrite/roomserver/storage"
|
||||
"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"
|
||||
)
|
||||
|
||||
func psqlConnectionString() config.DataSource {
|
||||
user := os.Getenv("POSTGRES_USER")
|
||||
if user == "" {
|
||||
user = "dendrite"
|
||||
}
|
||||
dbName := os.Getenv("POSTGRES_DB")
|
||||
if dbName == "" {
|
||||
dbName = "dendrite"
|
||||
}
|
||||
connStr := fmt.Sprintf(
|
||||
"user=%s dbname=%s sslmode=disable", user, dbName,
|
||||
)
|
||||
password := os.Getenv("POSTGRES_PASSWORD")
|
||||
if password != "" {
|
||||
connStr += fmt.Sprintf(" password=%s", password)
|
||||
}
|
||||
host := os.Getenv("POSTGRES_HOST")
|
||||
if host != "" {
|
||||
connStr += fmt.Sprintf(" host=%s", host)
|
||||
}
|
||||
return config.DataSource(connStr)
|
||||
var js nats.JetStreamContext
|
||||
var jc *nats.Conn
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
var pc *process.ProcessContext
|
||||
pc, js, jc = jetstream.PrepareForTests()
|
||||
code := m.Run()
|
||||
pc.ShutdownDendrite()
|
||||
pc.WaitForComponentsToFinish()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func TestSingleTransactionOnInput(t *testing.T) {
|
||||
|
|
@ -63,7 +54,7 @@ func TestSingleTransactionOnInput(t *testing.T) {
|
|||
}
|
||||
db, err := storage.Open(
|
||||
&config.DatabaseOptions{
|
||||
ConnectionString: psqlConnectionString(),
|
||||
ConnectionString: "",
|
||||
MaxOpenConnections: 1,
|
||||
MaxIdleConnections: 1,
|
||||
},
|
||||
|
|
@ -74,7 +65,9 @@ func TestSingleTransactionOnInput(t *testing.T) {
|
|||
t.SkipNow()
|
||||
}
|
||||
inputter := &input.Inputer{
|
||||
DB: db,
|
||||
DB: db,
|
||||
JetStream: js,
|
||||
NATSClient: jc,
|
||||
}
|
||||
res := &api.InputRoomEventsResponse{}
|
||||
inputter.InputRoomEvents(
|
||||
|
|
|
|||
709
roomserver/internal/perform/perform_upgrade.go
Normal file
709
roomserver/internal/perform/perform_upgrade.go
Normal file
|
|
@ -0,0 +1,709 @@
|
|||
// 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 perform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Upgrader struct {
|
||||
Cfg *config.RoomServer
|
||||
URSAPI api.RoomserverInternalAPI
|
||||
}
|
||||
|
||||
// fledglingEvent is a helper representation of an event used when creating many events in succession.
|
||||
type fledglingEvent struct {
|
||||
Type string `json:"type"`
|
||||
StateKey string `json:"state_key"`
|
||||
Content interface{} `json:"content"`
|
||||
}
|
||||
|
||||
// PerformRoomUpgrade upgrades a room from one version to another
|
||||
func (r *Upgrader) PerformRoomUpgrade(
|
||||
ctx context.Context,
|
||||
req *api.PerformRoomUpgradeRequest,
|
||||
res *api.PerformRoomUpgradeResponse,
|
||||
) {
|
||||
res.NewRoomID, res.Error = r.performRoomUpgrade(ctx, req)
|
||||
if res.Error != nil {
|
||||
res.NewRoomID = ""
|
||||
logrus.WithContext(ctx).WithError(res.Error).Error("Room upgrade failed")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Upgrader) performRoomUpgrade(
|
||||
ctx context.Context,
|
||||
req *api.PerformRoomUpgradeRequest,
|
||||
) (string, *api.PerformError) {
|
||||
roomID := req.RoomID
|
||||
userID := req.UserID
|
||||
evTime := time.Now()
|
||||
|
||||
// Return an immediate error if the room does not exist
|
||||
if err := r.validateRoomExists(ctx, roomID); err != nil {
|
||||
return "", &api.PerformError{
|
||||
Code: api.PerformErrorNoRoom,
|
||||
Msg: "Error validating that the room exists",
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Check if the user is authorized to actually perform the upgrade (can send m.room.tombstone)
|
||||
if !r.userIsAuthorized(ctx, userID, roomID) {
|
||||
return "", &api.PerformError{
|
||||
Code: api.PerformErrorNotAllowed,
|
||||
Msg: "You don't have permission to upgrade the room, power level too low.",
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (#267): Check room ID doesn't clash with an existing one, and we
|
||||
// probably shouldn't be using pseudo-random strings, maybe GUIDs?
|
||||
newRoomID := fmt.Sprintf("!%s:%s", util.RandomString(16), r.Cfg.Matrix.ServerName)
|
||||
|
||||
// Get the existing room state for the old room.
|
||||
oldRoomReq := &api.QueryLatestEventsAndStateRequest{
|
||||
RoomID: roomID,
|
||||
}
|
||||
oldRoomRes := &api.QueryLatestEventsAndStateResponse{}
|
||||
if err := r.URSAPI.QueryLatestEventsAndState(ctx, oldRoomReq, oldRoomRes); err != nil {
|
||||
return "", &api.PerformError{
|
||||
Msg: fmt.Sprintf("Failed to get latest state: %s", err),
|
||||
}
|
||||
}
|
||||
|
||||
// Make the tombstone event
|
||||
tombstoneEvent, pErr := r.makeTombstoneEvent(ctx, evTime, userID, roomID, newRoomID)
|
||||
if pErr != nil {
|
||||
return "", pErr
|
||||
}
|
||||
|
||||
// Generate the initial events we need to send into the new room. This includes copied state events and bans
|
||||
// as well as the power level events needed to set up the room
|
||||
eventsToMake, pErr := r.generateInitialEvents(ctx, oldRoomRes, userID, roomID, string(req.RoomVersion), tombstoneEvent)
|
||||
if pErr != nil {
|
||||
return "", pErr
|
||||
}
|
||||
|
||||
// 5. Send the tombstone event to the old room (must do this before we set the new canonical_alias)
|
||||
if pErr = r.sendHeaderedEvent(ctx, tombstoneEvent); pErr != nil {
|
||||
return "", pErr
|
||||
}
|
||||
|
||||
// Send the setup events to the new room
|
||||
if pErr = r.sendInitialEvents(ctx, evTime, userID, newRoomID, string(req.RoomVersion), eventsToMake); pErr != nil {
|
||||
return "", pErr
|
||||
}
|
||||
|
||||
// If the old room was public, make sure the new one is too
|
||||
if pErr = r.publishIfOldRoomWasPublic(ctx, roomID, newRoomID); pErr != nil {
|
||||
return "", pErr
|
||||
}
|
||||
|
||||
// If the old room had a canonical alias event, it should be deleted in the old room
|
||||
if pErr = r.clearOldCanonicalAliasEvent(ctx, oldRoomRes, evTime, userID, roomID); pErr != nil {
|
||||
return "", pErr
|
||||
}
|
||||
|
||||
// 4. Move local aliases to the new room
|
||||
if pErr = moveLocalAliases(ctx, roomID, newRoomID, userID, r.URSAPI); pErr != nil {
|
||||
return "", pErr
|
||||
}
|
||||
|
||||
// 6. Restrict power levels in the old room
|
||||
if pErr = r.restrictOldRoomPowerLevels(ctx, evTime, userID, roomID); pErr != nil {
|
||||
return "", pErr
|
||||
}
|
||||
|
||||
return newRoomID, nil
|
||||
}
|
||||
|
||||
func (r *Upgrader) getRoomPowerLevels(ctx context.Context, roomID string) (*gomatrixserverlib.PowerLevelContent, *api.PerformError) {
|
||||
oldPowerLevelsEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomPowerLevels,
|
||||
StateKey: "",
|
||||
})
|
||||
powerLevelContent, err := oldPowerLevelsEvent.PowerLevels()
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error()
|
||||
return nil, &api.PerformError{
|
||||
Msg: "powerLevel event was not actually a power level event",
|
||||
}
|
||||
}
|
||||
return powerLevelContent, nil
|
||||
}
|
||||
|
||||
func (r *Upgrader) restrictOldRoomPowerLevels(ctx context.Context, evTime time.Time, userID, roomID string) *api.PerformError {
|
||||
restrictedPowerLevelContent, pErr := r.getRoomPowerLevels(ctx, roomID)
|
||||
if pErr != nil {
|
||||
return pErr
|
||||
}
|
||||
|
||||
// From: https://spec.matrix.org/v1.2/client-server-api/#server-behaviour-16
|
||||
// If possible, the power levels in the old room should also be modified to
|
||||
// prevent sending of events and inviting new users. For example, setting
|
||||
// events_default and invite to the greater of 50 and users_default + 1.
|
||||
restrictedDefaultPowerLevel := int64(50)
|
||||
if restrictedPowerLevelContent.UsersDefault+1 > restrictedDefaultPowerLevel {
|
||||
restrictedDefaultPowerLevel = restrictedPowerLevelContent.UsersDefault + 1
|
||||
}
|
||||
restrictedPowerLevelContent.EventsDefault = restrictedDefaultPowerLevel
|
||||
restrictedPowerLevelContent.Invite = restrictedDefaultPowerLevel
|
||||
|
||||
restrictedPowerLevelsHeadered, resErr := r.makeHeaderedEvent(ctx, evTime, userID, roomID, fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomPowerLevels,
|
||||
StateKey: "",
|
||||
Content: restrictedPowerLevelContent,
|
||||
})
|
||||
if resErr != nil {
|
||||
if resErr.Code == api.PerformErrorNotAllowed {
|
||||
util.GetLogger(ctx).WithField(logrus.ErrorKey, resErr).Warn("UpgradeRoom: Could not restrict power levels in old room")
|
||||
} else {
|
||||
return resErr
|
||||
}
|
||||
} else {
|
||||
if resErr = r.sendHeaderedEvent(ctx, restrictedPowerLevelsHeadered); resErr != nil {
|
||||
return resErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func moveLocalAliases(ctx context.Context,
|
||||
roomID, newRoomID, userID string,
|
||||
URSAPI api.RoomserverInternalAPI) *api.PerformError {
|
||||
var err error
|
||||
|
||||
aliasReq := api.GetAliasesForRoomIDRequest{RoomID: roomID}
|
||||
aliasRes := api.GetAliasesForRoomIDResponse{}
|
||||
if err = URSAPI.GetAliasesForRoomID(ctx, &aliasReq, &aliasRes); err != nil {
|
||||
return &api.PerformError{
|
||||
Msg: "Could not get aliases for old room",
|
||||
}
|
||||
}
|
||||
|
||||
for _, alias := range aliasRes.Aliases {
|
||||
removeAliasReq := api.RemoveRoomAliasRequest{UserID: userID, Alias: alias}
|
||||
removeAliasRes := api.RemoveRoomAliasResponse{}
|
||||
if err = URSAPI.RemoveRoomAlias(ctx, &removeAliasReq, &removeAliasRes); err != nil {
|
||||
return &api.PerformError{
|
||||
Msg: "api.RemoveRoomAlias failed",
|
||||
}
|
||||
}
|
||||
|
||||
setAliasReq := api.SetRoomAliasRequest{UserID: userID, Alias: alias, RoomID: newRoomID}
|
||||
setAliasRes := api.SetRoomAliasResponse{}
|
||||
if err = URSAPI.SetRoomAlias(ctx, &setAliasReq, &setAliasRes); err != nil {
|
||||
return &api.PerformError{
|
||||
Msg: "api.SetRoomAlias failed",
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Upgrader) clearOldCanonicalAliasEvent(ctx context.Context, oldRoom *api.QueryLatestEventsAndStateResponse, evTime time.Time, userID, roomID string) *api.PerformError {
|
||||
for _, event := range oldRoom.StateEvents {
|
||||
if event.Type() != gomatrixserverlib.MRoomCanonicalAlias || !event.StateKeyEquals("") {
|
||||
continue
|
||||
}
|
||||
var aliasContent struct {
|
||||
Alias string `json:"alias"`
|
||||
AltAliases []string `json:"alt_aliases"`
|
||||
}
|
||||
if err := json.Unmarshal(event.Content(), &aliasContent); err != nil {
|
||||
return &api.PerformError{
|
||||
Msg: fmt.Sprintf("Failed to unmarshal canonical aliases: %s", err),
|
||||
}
|
||||
}
|
||||
if aliasContent.Alias == "" && len(aliasContent.AltAliases) == 0 {
|
||||
// There are no canonical aliases to clear, therefore do nothing.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
emptyCanonicalAliasEvent, resErr := r.makeHeaderedEvent(ctx, evTime, userID, roomID, fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomCanonicalAlias,
|
||||
Content: map[string]interface{}{},
|
||||
})
|
||||
if resErr != nil {
|
||||
if resErr.Code == api.PerformErrorNotAllowed {
|
||||
util.GetLogger(ctx).WithField(logrus.ErrorKey, resErr).Warn("UpgradeRoom: Could not set empty canonical alias event in old room")
|
||||
} else {
|
||||
return resErr
|
||||
}
|
||||
} else {
|
||||
if resErr = r.sendHeaderedEvent(ctx, emptyCanonicalAliasEvent); resErr != nil {
|
||||
return resErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Upgrader) publishIfOldRoomWasPublic(ctx context.Context, roomID, newRoomID string) *api.PerformError {
|
||||
// check if the old room was published
|
||||
var pubQueryRes api.QueryPublishedRoomsResponse
|
||||
err := r.URSAPI.QueryPublishedRooms(ctx, &api.QueryPublishedRoomsRequest{
|
||||
RoomID: roomID,
|
||||
}, &pubQueryRes)
|
||||
if err != nil {
|
||||
return &api.PerformError{
|
||||
Msg: "QueryPublishedRooms failed",
|
||||
}
|
||||
}
|
||||
|
||||
// if the old room is published (was public), publish the new room
|
||||
if len(pubQueryRes.RoomIDs) == 1 {
|
||||
publishNewRoomAndUnpublishOldRoom(ctx, r.URSAPI, roomID, newRoomID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func publishNewRoomAndUnpublishOldRoom(
|
||||
ctx context.Context,
|
||||
URSAPI api.RoomserverInternalAPI,
|
||||
oldRoomID, newRoomID string,
|
||||
) {
|
||||
// expose this room in the published room list
|
||||
var pubNewRoomRes api.PerformPublishResponse
|
||||
URSAPI.PerformPublish(ctx, &api.PerformPublishRequest{
|
||||
RoomID: newRoomID,
|
||||
Visibility: "public",
|
||||
}, &pubNewRoomRes)
|
||||
if pubNewRoomRes.Error != nil {
|
||||
// treat as non-fatal since the room is already made by this point
|
||||
util.GetLogger(ctx).WithError(pubNewRoomRes.Error).Error("failed to visibility:public")
|
||||
}
|
||||
|
||||
var unpubOldRoomRes api.PerformPublishResponse
|
||||
// remove the old room from the published room list
|
||||
URSAPI.PerformPublish(ctx, &api.PerformPublishRequest{
|
||||
RoomID: oldRoomID,
|
||||
Visibility: "private",
|
||||
}, &unpubOldRoomRes)
|
||||
if unpubOldRoomRes.Error != nil {
|
||||
// treat as non-fatal since the room is already made by this point
|
||||
util.GetLogger(ctx).WithError(unpubOldRoomRes.Error).Error("failed to visibility:private")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Upgrader) validateRoomExists(ctx context.Context, roomID string) error {
|
||||
verReq := api.QueryRoomVersionForRoomRequest{RoomID: roomID}
|
||||
verRes := api.QueryRoomVersionForRoomResponse{}
|
||||
if err := r.URSAPI.QueryRoomVersionForRoom(ctx, &verReq, &verRes); err != nil {
|
||||
return &api.PerformError{
|
||||
Code: api.PerformErrorNoRoom,
|
||||
Msg: "Room does not exist",
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Upgrader) userIsAuthorized(ctx context.Context, userID, roomID string,
|
||||
) bool {
|
||||
plEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomPowerLevels,
|
||||
StateKey: "",
|
||||
})
|
||||
if plEvent == nil {
|
||||
return false
|
||||
}
|
||||
pl, err := plEvent.PowerLevels()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// Check for power level required to send tombstone event (marks the current room as obsolete),
|
||||
// if not found, use the StateDefault power level
|
||||
return pl.UserLevel(userID) >= pl.EventLevel("m.room.tombstone", true)
|
||||
}
|
||||
|
||||
// nolint:gocyclo
|
||||
func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.QueryLatestEventsAndStateResponse, userID, roomID, newVersion string, tombstoneEvent *gomatrixserverlib.HeaderedEvent) ([]fledglingEvent, *api.PerformError) {
|
||||
state := make(map[gomatrixserverlib.StateKeyTuple]*gomatrixserverlib.HeaderedEvent, len(oldRoom.StateEvents))
|
||||
for _, event := range oldRoom.StateEvents {
|
||||
if event.StateKey() == nil {
|
||||
// This shouldn't ever happen, but better to be safe than sorry.
|
||||
continue
|
||||
}
|
||||
if event.Type() == gomatrixserverlib.MRoomMember && !event.StateKeyEquals(userID) {
|
||||
// With the exception of bans and invites which we do want to copy, we
|
||||
// should ignore membership events that aren't our own, as event auth will
|
||||
// prevent us from being able to create membership events on behalf of other
|
||||
// users anyway unless they are invites or bans.
|
||||
membership, err := event.Membership()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
switch membership {
|
||||
case gomatrixserverlib.Ban:
|
||||
case gomatrixserverlib.Invite:
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
state[gomatrixserverlib.StateKeyTuple{EventType: event.Type(), StateKey: *event.StateKey()}] = event
|
||||
}
|
||||
|
||||
// The following events are ones that we are going to override manually
|
||||
// in the following section.
|
||||
override := map[gomatrixserverlib.StateKeyTuple]struct{}{
|
||||
{EventType: gomatrixserverlib.MRoomCreate, StateKey: ""}: {},
|
||||
{EventType: gomatrixserverlib.MRoomMember, StateKey: userID}: {},
|
||||
{EventType: gomatrixserverlib.MRoomPowerLevels, StateKey: ""}: {},
|
||||
{EventType: gomatrixserverlib.MRoomJoinRules, StateKey: ""}: {},
|
||||
}
|
||||
|
||||
// The overridden events are essential events that must be present in the
|
||||
// old room state. Check that they are there.
|
||||
for tuple := range override {
|
||||
if _, ok := state[tuple]; !ok {
|
||||
return nil, &api.PerformError{
|
||||
Msg: fmt.Sprintf("Essential event of type %q state key %q is missing", tuple.EventType, tuple.StateKey),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
oldCreateEvent := state[gomatrixserverlib.StateKeyTuple{EventType: gomatrixserverlib.MRoomCreate, StateKey: ""}]
|
||||
oldMembershipEvent := state[gomatrixserverlib.StateKeyTuple{EventType: gomatrixserverlib.MRoomMember, StateKey: userID}]
|
||||
oldPowerLevelsEvent := state[gomatrixserverlib.StateKeyTuple{EventType: gomatrixserverlib.MRoomPowerLevels, StateKey: ""}]
|
||||
oldJoinRulesEvent := state[gomatrixserverlib.StateKeyTuple{EventType: gomatrixserverlib.MRoomJoinRules, StateKey: ""}]
|
||||
|
||||
// Create the new room create event. Using a map here instead of CreateContent
|
||||
// means that we preserve any other interesting fields that might be present
|
||||
// in the create event (such as for the room types MSC).
|
||||
newCreateContent := map[string]interface{}{}
|
||||
_ = json.Unmarshal(oldCreateEvent.Content(), &newCreateContent)
|
||||
newCreateContent["creator"] = userID
|
||||
newCreateContent["room_version"] = newVersion
|
||||
newCreateContent["predecessor"] = gomatrixserverlib.PreviousRoom{
|
||||
EventID: tombstoneEvent.EventID(),
|
||||
RoomID: roomID,
|
||||
}
|
||||
newCreateEvent := fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomCreate,
|
||||
StateKey: "",
|
||||
Content: newCreateContent,
|
||||
}
|
||||
|
||||
// Now create the new membership event. Same rules apply as above, so
|
||||
// that we preserve fields we don't otherwise know about. We'll always
|
||||
// set the membership to join though, because that is necessary to auth
|
||||
// the events after it.
|
||||
newMembershipContent := map[string]interface{}{}
|
||||
_ = json.Unmarshal(oldMembershipEvent.Content(), &newMembershipContent)
|
||||
newMembershipContent["membership"] = gomatrixserverlib.Join
|
||||
newMembershipEvent := fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomMember,
|
||||
StateKey: userID,
|
||||
Content: newMembershipContent,
|
||||
}
|
||||
|
||||
// We might need to temporarily give ourselves a higher power level
|
||||
// than we had in the old room in order to be able to send all of
|
||||
// the relevant state events. This function will return whether we
|
||||
// had to override the power level events or not — if we did, we
|
||||
// need to send the original power levels again later on.
|
||||
powerLevelContent, err := oldPowerLevelsEvent.PowerLevels()
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error()
|
||||
return nil, &api.PerformError{
|
||||
Msg: "Power level event content was invalid",
|
||||
}
|
||||
}
|
||||
tempPowerLevelsEvent, powerLevelsOverridden := createTemporaryPowerLevels(powerLevelContent, userID)
|
||||
|
||||
// Now do the join rules event, same as the create and membership
|
||||
// events. We'll set a sane default of "invite" so that if the
|
||||
// existing join rules contains garbage, the room can still be
|
||||
// upgraded.
|
||||
newJoinRulesContent := map[string]interface{}{
|
||||
"join_rule": gomatrixserverlib.Invite, // sane default
|
||||
}
|
||||
_ = json.Unmarshal(oldJoinRulesEvent.Content(), &newJoinRulesContent)
|
||||
newJoinRulesEvent := fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomJoinRules,
|
||||
StateKey: "",
|
||||
Content: newJoinRulesContent,
|
||||
}
|
||||
|
||||
eventsToMake := make([]fledglingEvent, 0, len(state))
|
||||
eventsToMake = append(
|
||||
eventsToMake, newCreateEvent, newMembershipEvent,
|
||||
tempPowerLevelsEvent, newJoinRulesEvent,
|
||||
)
|
||||
|
||||
// For some reason Sytest expects there to be a guest access event.
|
||||
// Create one if it doesn't exist.
|
||||
if _, ok := state[gomatrixserverlib.StateKeyTuple{EventType: gomatrixserverlib.MRoomGuestAccess, StateKey: ""}]; !ok {
|
||||
eventsToMake = append(eventsToMake, fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomGuestAccess,
|
||||
Content: map[string]string{
|
||||
"guest_access": "forbidden",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Duplicate all of the old state events into the new room.
|
||||
for tuple, event := range state {
|
||||
if _, ok := override[tuple]; ok {
|
||||
// Don't duplicate events we have overridden already. They
|
||||
// are already in `eventsToMake`.
|
||||
continue
|
||||
}
|
||||
newEvent := fledglingEvent{
|
||||
Type: tuple.EventType,
|
||||
StateKey: tuple.StateKey,
|
||||
}
|
||||
if err = json.Unmarshal(event.Content(), &newEvent.Content); err != nil {
|
||||
logrus.WithError(err).Error("Failed to unmarshal old event")
|
||||
continue
|
||||
}
|
||||
eventsToMake = append(eventsToMake, newEvent)
|
||||
}
|
||||
|
||||
// If we sent a temporary power level event into the room before,
|
||||
// override that now by restoring the original power levels.
|
||||
if powerLevelsOverridden {
|
||||
eventsToMake = append(eventsToMake, fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomPowerLevels,
|
||||
Content: powerLevelContent,
|
||||
})
|
||||
}
|
||||
return eventsToMake, nil
|
||||
}
|
||||
|
||||
func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, userID, newRoomID, newVersion string, eventsToMake []fledglingEvent) *api.PerformError {
|
||||
var err error
|
||||
var builtEvents []*gomatrixserverlib.HeaderedEvent
|
||||
authEvents := gomatrixserverlib.NewAuthEvents(nil)
|
||||
for i, e := range eventsToMake {
|
||||
depth := i + 1 // depth starts at 1
|
||||
|
||||
builder := gomatrixserverlib.EventBuilder{
|
||||
Sender: userID,
|
||||
RoomID: newRoomID,
|
||||
Type: e.Type,
|
||||
StateKey: &e.StateKey,
|
||||
Depth: int64(depth),
|
||||
}
|
||||
err = builder.SetContent(e.Content)
|
||||
if err != nil {
|
||||
return &api.PerformError{
|
||||
Msg: "builder.SetContent failed",
|
||||
}
|
||||
}
|
||||
if i > 0 {
|
||||
builder.PrevEvents = []gomatrixserverlib.EventReference{builtEvents[i-1].EventReference()}
|
||||
}
|
||||
var event *gomatrixserverlib.Event
|
||||
event, err = r.buildEvent(&builder, &authEvents, evTime, gomatrixserverlib.RoomVersion(newVersion))
|
||||
if err != nil {
|
||||
return &api.PerformError{
|
||||
Msg: "buildEvent failed",
|
||||
}
|
||||
}
|
||||
|
||||
if err = gomatrixserverlib.Allowed(event, &authEvents); err != nil {
|
||||
return &api.PerformError{
|
||||
Msg: "gomatrixserverlib.Allowed failed",
|
||||
}
|
||||
}
|
||||
|
||||
// Add the event to the list of auth events
|
||||
builtEvents = append(builtEvents, event.Headered(gomatrixserverlib.RoomVersion(newVersion)))
|
||||
err = authEvents.AddEvent(event)
|
||||
if err != nil {
|
||||
return &api.PerformError{
|
||||
Msg: "authEvents.AddEvent failed",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputs := make([]api.InputRoomEvent, 0, len(builtEvents))
|
||||
for _, event := range builtEvents {
|
||||
inputs = append(inputs, api.InputRoomEvent{
|
||||
Kind: api.KindNew,
|
||||
Event: event,
|
||||
Origin: r.Cfg.Matrix.ServerName,
|
||||
SendAsServer: api.DoNotSendToOtherServers,
|
||||
})
|
||||
}
|
||||
if err = api.SendInputRoomEvents(ctx, r.URSAPI, inputs, false); err != nil {
|
||||
return &api.PerformError{
|
||||
Msg: "api.SendInputRoomEvents failed",
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Upgrader) makeTombstoneEvent(
|
||||
ctx context.Context,
|
||||
evTime time.Time,
|
||||
userID, roomID, newRoomID string,
|
||||
) (*gomatrixserverlib.HeaderedEvent, *api.PerformError) {
|
||||
content := map[string]interface{}{
|
||||
"body": "This room has been replaced",
|
||||
"replacement_room": newRoomID,
|
||||
}
|
||||
event := fledglingEvent{
|
||||
Type: "m.room.tombstone",
|
||||
Content: content,
|
||||
}
|
||||
return r.makeHeaderedEvent(ctx, evTime, userID, roomID, event)
|
||||
}
|
||||
|
||||
func (r *Upgrader) makeHeaderedEvent(ctx context.Context, evTime time.Time, userID, roomID string, event fledglingEvent) (*gomatrixserverlib.HeaderedEvent, *api.PerformError) {
|
||||
builder := gomatrixserverlib.EventBuilder{
|
||||
Sender: userID,
|
||||
RoomID: roomID,
|
||||
Type: event.Type,
|
||||
StateKey: &event.StateKey,
|
||||
}
|
||||
err := builder.SetContent(event.Content)
|
||||
if err != nil {
|
||||
return nil, &api.PerformError{
|
||||
Msg: "builder.SetContent failed",
|
||||
}
|
||||
}
|
||||
var queryRes api.QueryLatestEventsAndStateResponse
|
||||
headeredEvent, err := eventutil.QueryAndBuildEvent(ctx, &builder, r.Cfg.Matrix, evTime, r.URSAPI, &queryRes)
|
||||
if err == eventutil.ErrRoomNoExists {
|
||||
return nil, &api.PerformError{
|
||||
Code: api.PerformErrorNoRoom,
|
||||
Msg: "Room does not exist",
|
||||
}
|
||||
} else if e, ok := err.(gomatrixserverlib.BadJSONError); ok {
|
||||
return nil, &api.PerformError{
|
||||
Msg: e.Error(),
|
||||
}
|
||||
} else if e, ok := err.(gomatrixserverlib.EventValidationError); ok {
|
||||
if e.Code == gomatrixserverlib.EventValidationTooLarge {
|
||||
return nil, &api.PerformError{
|
||||
Msg: e.Error(),
|
||||
}
|
||||
}
|
||||
return nil, &api.PerformError{
|
||||
Msg: e.Error(),
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, &api.PerformError{
|
||||
Msg: "eventutil.BuildEvent failed",
|
||||
}
|
||||
}
|
||||
// check to see if this user can perform this operation
|
||||
stateEvents := make([]*gomatrixserverlib.Event, len(queryRes.StateEvents))
|
||||
for i := range queryRes.StateEvents {
|
||||
stateEvents[i] = queryRes.StateEvents[i].Event
|
||||
}
|
||||
provider := gomatrixserverlib.NewAuthEvents(stateEvents)
|
||||
if err = gomatrixserverlib.Allowed(headeredEvent.Event, &provider); err != nil {
|
||||
return nil, &api.PerformError{
|
||||
Code: api.PerformErrorNotAllowed,
|
||||
Msg: err.Error(), // TODO: Is this error string comprehensible to the client?
|
||||
}
|
||||
}
|
||||
|
||||
return headeredEvent, nil
|
||||
}
|
||||
|
||||
func createTemporaryPowerLevels(powerLevelContent *gomatrixserverlib.PowerLevelContent, userID string) (fledglingEvent, bool) {
|
||||
// Work out what power level we need in order to be able to send events
|
||||
// of all types into the room.
|
||||
neededPowerLevel := powerLevelContent.StateDefault
|
||||
for _, powerLevel := range powerLevelContent.Events {
|
||||
if powerLevel > neededPowerLevel {
|
||||
neededPowerLevel = powerLevel
|
||||
}
|
||||
}
|
||||
|
||||
// Make a copy of the existing power level content.
|
||||
tempPowerLevelContent := *powerLevelContent
|
||||
powerLevelsOverridden := false
|
||||
|
||||
// At this point, the "Users", "Events" and "Notifications" keys are all
|
||||
// pointing to the map of the original PL content, so we will specifically
|
||||
// override the users map with a new one and duplicate the values deeply,
|
||||
// so that we can modify them without modifying the original.
|
||||
tempPowerLevelContent.Users = make(map[string]int64, len(powerLevelContent.Users))
|
||||
for key, value := range powerLevelContent.Users {
|
||||
tempPowerLevelContent.Users[key] = value
|
||||
}
|
||||
|
||||
// If the user who is upgrading the room doesn't already have sufficient
|
||||
// power, then elevate their power levels.
|
||||
if tempPowerLevelContent.UserLevel(userID) < neededPowerLevel {
|
||||
tempPowerLevelContent.Users[userID] = neededPowerLevel
|
||||
powerLevelsOverridden = true
|
||||
}
|
||||
|
||||
// Then return the temporary power levels event.
|
||||
return fledglingEvent{
|
||||
Type: gomatrixserverlib.MRoomPowerLevels,
|
||||
Content: tempPowerLevelContent,
|
||||
}, powerLevelsOverridden
|
||||
}
|
||||
|
||||
func (r *Upgrader) sendHeaderedEvent(
|
||||
ctx context.Context,
|
||||
headeredEvent *gomatrixserverlib.HeaderedEvent,
|
||||
) *api.PerformError {
|
||||
var inputs []api.InputRoomEvent
|
||||
inputs = append(inputs, api.InputRoomEvent{
|
||||
Kind: api.KindNew,
|
||||
Event: headeredEvent,
|
||||
Origin: r.Cfg.Matrix.ServerName,
|
||||
SendAsServer: api.DoNotSendToOtherServers,
|
||||
})
|
||||
if err := api.SendInputRoomEvents(ctx, r.URSAPI, inputs, false); err != nil {
|
||||
return &api.PerformError{
|
||||
Msg: "api.SendInputRoomEvents failed",
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Upgrader) buildEvent(
|
||||
builder *gomatrixserverlib.EventBuilder,
|
||||
provider gomatrixserverlib.AuthEventProvider,
|
||||
evTime time.Time,
|
||||
roomVersion gomatrixserverlib.RoomVersion,
|
||||
) (*gomatrixserverlib.Event, error) {
|
||||
eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(builder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
refs, err := eventsNeeded.AuthEventReferences(provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
builder.AuthEvents = refs
|
||||
event, err := builder.Build(
|
||||
evTime, r.Cfg.Matrix.ServerName, r.Cfg.Matrix.KeyID,
|
||||
r.Cfg.Matrix.PrivateKey, roomVersion,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot build event %s : Builder failed to build. %w", builder.Type, err)
|
||||
}
|
||||
return event, nil
|
||||
}
|
||||
|
|
@ -220,7 +220,7 @@ func (r *Queryer) QueryMembershipsForRoom(
|
|||
if request.Sender == "" {
|
||||
var events []types.Event
|
||||
var eventNIDs []types.EventNID
|
||||
eventNIDs, err = r.DB.GetMembershipEventNIDsForRoom(ctx, info.RoomNID, request.JoinedOnly, false)
|
||||
eventNIDs, err = r.DB.GetMembershipEventNIDsForRoom(ctx, info.RoomNID, request.JoinedOnly, request.LocalOnly)
|
||||
if err != nil {
|
||||
return fmt.Errorf("r.DB.GetMembershipEventNIDsForRoom: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ const (
|
|||
RoomserverPerformInvitePath = "/roomserver/performInvite"
|
||||
RoomserverPerformPeekPath = "/roomserver/performPeek"
|
||||
RoomserverPerformUnpeekPath = "/roomserver/performUnpeek"
|
||||
RoomserverPerformRoomUpgradePath = "/roomserver/performRoomUpgrade"
|
||||
RoomserverPerformJoinPath = "/roomserver/performJoin"
|
||||
RoomserverPerformLeavePath = "/roomserver/performLeave"
|
||||
RoomserverPerformBackfillPath = "/roomserver/performBackfill"
|
||||
|
|
@ -252,6 +253,23 @@ func (h *httpRoomserverInternalAPI) PerformUnpeek(
|
|||
}
|
||||
}
|
||||
|
||||
func (h *httpRoomserverInternalAPI) PerformRoomUpgrade(
|
||||
ctx context.Context,
|
||||
request *api.PerformRoomUpgradeRequest,
|
||||
response *api.PerformRoomUpgradeResponse,
|
||||
) {
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, "PerformRoomUpgrade")
|
||||
defer span.Finish()
|
||||
|
||||
apiURL := h.roomserverURL + RoomserverPerformRoomUpgradePath
|
||||
err := httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
|
||||
if err != nil {
|
||||
response.Error = &api.PerformError{
|
||||
Msg: fmt.Sprintf("failed to communicate with roomserver: %s", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *httpRoomserverInternalAPI) PerformLeave(
|
||||
ctx context.Context,
|
||||
request *api.PerformLeaveRequest,
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue