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
|
timeout-minutes: 5
|
||||||
name: Unit tests (Go ${{ matrix.go }})
|
name: Unit tests (Go ${{ matrix.go }})
|
||||||
runs-on: ubuntu-latest
|
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:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
|
@ -91,7 +111,12 @@ jobs:
|
||||||
key: ${{ runner.os }}-go${{ matrix.go }}-test-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-go${{ matrix.go }}-test-${{ hashFiles('**/go.sum') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-go${{ matrix.go }}-test-
|
${{ 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 Dendrite for linux with different architectures and go versions
|
||||||
build:
|
build:
|
||||||
|
|
@ -233,7 +258,14 @@ jobs:
|
||||||
- name: Summarise results.tap
|
- name: Summarise results.tap
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
run: /sytest/scripts/tap_to_gha.pl /logs/results.tap
|
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
|
- name: Upload Sytest logs
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
|
|
|
||||||
27
CHANGES.md
27
CHANGES.md
|
|
@ -1,5 +1,32 @@
|
||||||
# Changelog
|
# 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)
|
## Dendrite 0.7.0 (2022-03-25)
|
||||||
|
|
||||||
### Features
|
### 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
|
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
|
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
|
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:
|
servers such as matrix.org reasonably well, although there are still some missing features (like Search).
|
||||||
|
|
||||||
- Search
|
|
||||||
- User Directory
|
|
||||||
- Presence
|
|
||||||
|
|
||||||
We are prioritising features that will benefit single-user homeservers first (e.g Receipts, E2E) rather
|
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).
|
than features that massive deployments may be interested in (User Directory, OpenID, Guests, Admin APIs, AS API).
|
||||||
This means Dendrite supports amongst others:
|
This means Dendrite supports amongst others:
|
||||||
|
|
||||||
- Core room functionality (creating rooms, invites, auth rules)
|
- 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
|
- Backfilling locally and via federation
|
||||||
- Accounts, Profiles and Devices
|
- Accounts, Profiles and Devices
|
||||||
- Published room lists
|
- Published room lists
|
||||||
|
|
@ -108,6 +105,8 @@ This means Dendrite supports amongst others:
|
||||||
- Receipts
|
- Receipts
|
||||||
- Push
|
- Push
|
||||||
- Guests
|
- Guests
|
||||||
|
- User Directory
|
||||||
|
- Presence
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,20 +83,29 @@ func (s *OutputRoomEventConsumer) onMessage(ctx context.Context, msg *nats.Msg)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if output.Type != api.OutputTypeNewRoomEvent {
|
if output.Type != api.OutputTypeNewRoomEvent || output.NewRoomEvent == nil {
|
||||||
return true
|
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 {
|
if len(output.NewRoomEvent.AddsStateEventIDs) > 0 {
|
||||||
eventsReq := &api.QueryEventsByIDRequest{
|
eventsReq := &api.QueryEventsByIDRequest{
|
||||||
EventIDs: output.NewRoomEvent.AddsStateEventIDs,
|
EventIDs: make([]string, 0, len(output.NewRoomEvent.AddsStateEventIDs)),
|
||||||
}
|
}
|
||||||
eventsRes := &api.QueryEventsByIDResponse{}
|
eventsRes := &api.QueryEventsByIDResponse{}
|
||||||
if err := s.rsAPI.QueryEventsByID(s.ctx, eventsReq, eventsRes); err != nil {
|
for _, eventID := range output.NewRoomEvent.AddsStateEventIDs {
|
||||||
return false
|
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
|
// 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
|
rst Both GET and PUT work
|
||||||
rct POST /rooms/:room_id/receipt can create receipts
|
rct POST /rooms/:room_id/receipt can create receipts
|
||||||
red POST /rooms/:room_id/read_markers can create read marker
|
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 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
|
med GET /media/r0/download can fetch the value again
|
||||||
cap GET /capabilities is present and well formed for registered user
|
cap GET /capabilities is present and well formed for registered user
|
||||||
cap GET /r0/capabilities is not public
|
cap GET /r0/capabilities is not public
|
||||||
|
cap GET /v3/capabilities is not public
|
||||||
reg Register with a recaptcha
|
reg Register with a recaptcha
|
||||||
reg registration is idempotent, without username specified
|
reg registration is idempotent, without username specified
|
||||||
reg registration is idempotent, with 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
|
ali Room aliases can contain Unicode
|
||||||
f,ali Remote room alias queries can handle Unicode
|
f,ali Remote room alias queries can handle Unicode
|
||||||
ali Canonical alias can be set
|
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
|
||||||
|
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 in the default room configuration
|
||||||
ali Regular users can add and delete aliases when m.room.aliases is restricted
|
ali Regular users can add and delete aliases when m.room.aliases is restricted
|
||||||
ali Deleting a non-existent alias should return a 404
|
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 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 Inbound federation rejects invite rejections which include invalid JSON for room version 6
|
||||||
rmv Server rejects invalid JSON in a version 6 room
|
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
|
pre Presence changes are reported to local room members
|
||||||
f,pre Presence changes are also reported to remote room members
|
f,pre Presence changes are also reported to remote room members
|
||||||
pre Presence changes to UNAVAILABLE are reported to local 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 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 via AS
|
||||||
app AS-ghosted users can use rooms themselves
|
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 Ghost user must register before joining room
|
||||||
app AS can set avatar for ghosted users
|
app AS can set avatar for ghosted users
|
||||||
app AS can set displayname for ghosted users
|
app AS can set displayname for ghosted users
|
||||||
app AS can't set displayname for random users
|
app AS can't set displayname for random users
|
||||||
app Inviting an AS-hosted user asks the AS server
|
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
|
||||||
|
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 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
|
||||||
app AS user (not ghost) can join room without registering, with user_id query param
|
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 and main public room lists are separate
|
||||||
app AS can deactivate a user
|
app AS can deactivate a user
|
||||||
psh Test that a message is pushed
|
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 Invites are pushed
|
||||||
psh Rooms with names are correctly named in pushed
|
psh Rooms with names are correctly named in pushed
|
||||||
psh Rooms with canonical alias 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)
|
crm Rooms can be created with an initial invite list (SYN-205) (1 subtests)
|
||||||
typ Typing notifications don't leak
|
typ Typing notifications don't leak
|
||||||
ban Non-present room members cannot ban others
|
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
|
||||||
|
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
|
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
|
||||||
|
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 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 Real user can call /events on another world_readable room (SYN-606)
|
||||||
gst Events come down the correct room
|
gst Events come down the correct room
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
import argparse
|
import argparse
|
||||||
import re
|
import re
|
||||||
import sys
|
import os
|
||||||
|
|
||||||
# Usage: $ ./are-we-synapse-yet.py [-v] results.tap
|
# Usage: $ ./are-we-synapse-yet.py [-v] results.tap
|
||||||
# This script scans a results.tap file from Dendrite's CI process and spits out
|
# 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
|
# ✓ POST /register downcases capitals in usernames
|
||||||
# ...
|
# ...
|
||||||
def print_stats(header_name, gid_to_tests, gid_to_name, verbose):
|
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)
|
subsections = [] # Registration: 100% (13/13 tests)
|
||||||
subsection_test_names = {} # 'subsection name': ["✓ Test 1", "✓ Test 2", "× Test 3"]
|
subsection_test_names = {} # 'subsection name': ["✓ Test 1", "✓ Test 2", "× Test 3"]
|
||||||
total_passing = 0
|
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():
|
for name, passing in tests.items():
|
||||||
if passing:
|
if passing:
|
||||||
group_passing += 1
|
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_tests += group_total
|
||||||
total_passing += group_passing
|
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("%s: %s (%d/%d tests)" % (header_name, pct, total_passing, total_tests))
|
||||||
print("-" * (len(header_name)+1))
|
print("-" * (len(header_name)+1))
|
||||||
for line in subsections:
|
for line in subsections:
|
||||||
print(" %s" % (line,))
|
print("%s%s" % ("::group::" if ci and verbose else "", line,))
|
||||||
if verbose:
|
if verbose:
|
||||||
for test_name_and_pass_mark in subsection_test_names[line]:
|
for test_name_and_pass_mark in subsection_test_names[line]:
|
||||||
print(" %s" % (test_name_and_pass_mark,))
|
print(" %s" % (test_name_and_pass_mark,))
|
||||||
print("")
|
print("%s" % ("::endgroup::" if ci else ""))
|
||||||
print("")
|
print("")
|
||||||
|
|
||||||
def main(results_tap_path, verbose):
|
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
|
- matrix.org
|
||||||
- vector.im
|
- 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
|
# Configuration for NATS JetStream
|
||||||
jetstream:
|
jetstream:
|
||||||
# A list of NATS Server addresses to connect to. If none are specified, an
|
# A list of NATS Server addresses to connect to. If none are specified, an
|
||||||
|
|
@ -160,12 +171,6 @@ client_api:
|
||||||
threshold: 5
|
threshold: 5
|
||||||
cooloff_ms: 500
|
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.
|
# Configuration for the Federation API.
|
||||||
federation_api:
|
federation_api:
|
||||||
internal_api:
|
internal_api:
|
||||||
|
|
@ -179,12 +184,6 @@ federation_api:
|
||||||
max_idle_conns: 2
|
max_idle_conns: 2
|
||||||
conn_max_lifetime: -1
|
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
|
# 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.
|
# backoff is 2**x seconds, so 1 = 2 seconds, 2 = 4 seconds, 3 = 8 seconds etc.
|
||||||
send_max_retries: 16
|
send_max_retries: 16
|
||||||
|
|
|
||||||
|
|
@ -84,18 +84,6 @@ services:
|
||||||
- internal
|
- internal
|
||||||
restart: unless-stopped
|
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:
|
federation_api:
|
||||||
hostname: federation_api
|
hostname: federation_api
|
||||||
image: matrixdotorg/dendrite-polylith:latest
|
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
|
package gobind
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -9,7 +23,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -22,11 +35,9 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/userutil"
|
"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/conn"
|
||||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/rooms"
|
"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/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"
|
||||||
"github.com/matrix-org/dendrite/federationapi/api"
|
|
||||||
"github.com/matrix-org/dendrite/internal/httputil"
|
"github.com/matrix-org/dendrite/internal/httputil"
|
||||||
"github.com/matrix-org/dendrite/keyserver"
|
"github.com/matrix-org/dendrite/keyserver"
|
||||||
"github.com/matrix-org/dendrite/roomserver"
|
"github.com/matrix-org/dendrite/roomserver"
|
||||||
|
|
@ -41,6 +52,7 @@ import (
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
"golang.org/x/net/http2/h2c"
|
"golang.org/x/net/http2/h2c"
|
||||||
|
|
||||||
|
pineconeConnections "github.com/matrix-org/pinecone/connections"
|
||||||
pineconeMulticast "github.com/matrix-org/pinecone/multicast"
|
pineconeMulticast "github.com/matrix-org/pinecone/multicast"
|
||||||
pineconeRouter "github.com/matrix-org/pinecone/router"
|
pineconeRouter "github.com/matrix-org/pinecone/router"
|
||||||
pineconeSessions "github.com/matrix-org/pinecone/sessions"
|
pineconeSessions "github.com/matrix-org/pinecone/sessions"
|
||||||
|
|
@ -60,11 +72,9 @@ type DendriteMonolith struct {
|
||||||
PineconeRouter *pineconeRouter.Router
|
PineconeRouter *pineconeRouter.Router
|
||||||
PineconeMulticast *pineconeMulticast.Multicast
|
PineconeMulticast *pineconeMulticast.Multicast
|
||||||
PineconeQUIC *pineconeSessions.Sessions
|
PineconeQUIC *pineconeSessions.Sessions
|
||||||
|
PineconeManager *pineconeConnections.ConnectionManager
|
||||||
StorageDirectory string
|
StorageDirectory string
|
||||||
CacheDirectory string
|
CacheDirectory string
|
||||||
staticPeerURI string
|
|
||||||
staticPeerMutex sync.RWMutex
|
|
||||||
staticPeerAttempt chan struct{}
|
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
httpServer *http.Server
|
httpServer *http.Server
|
||||||
processContext *process.ProcessContext
|
processContext *process.ProcessContext
|
||||||
|
|
@ -80,7 +90,7 @@ func (m *DendriteMonolith) PeerCount(peertype int) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DendriteMonolith) SessionCount() int {
|
func (m *DendriteMonolith) SessionCount() int {
|
||||||
return len(m.PineconeQUIC.Sessions())
|
return len(m.PineconeQUIC.Protocol("matrix").Sessions())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DendriteMonolith) SetMulticastEnabled(enabled bool) {
|
func (m *DendriteMonolith) SetMulticastEnabled(enabled bool) {
|
||||||
|
|
@ -93,15 +103,8 @@ func (m *DendriteMonolith) SetMulticastEnabled(enabled bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DendriteMonolith) SetStaticPeer(uri string) {
|
func (m *DendriteMonolith) SetStaticPeer(uri string) {
|
||||||
m.staticPeerMutex.Lock()
|
m.PineconeManager.RemovePeers()
|
||||||
m.staticPeerURI = strings.TrimSpace(uri)
|
m.PineconeManager.AddPeer(strings.TrimSpace(uri))
|
||||||
m.staticPeerMutex.Unlock()
|
|
||||||
m.DisconnectType(int(pineconeRouter.PeerTypeRemote))
|
|
||||||
if uri != "" {
|
|
||||||
go func() {
|
|
||||||
m.staticPeerAttempt <- struct{}{}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DendriteMonolith) DisconnectType(peertype int) {
|
func (m *DendriteMonolith) DisconnectType(peertype int) {
|
||||||
|
|
@ -199,43 +202,6 @@ func (m *DendriteMonolith) RegisterDevice(localpart, deviceID string) (string, e
|
||||||
return loginRes.Device.AccessToken, nil
|
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
|
// nolint:gocyclo
|
||||||
func (m *DendriteMonolith) Start() {
|
func (m *DendriteMonolith) Start() {
|
||||||
var err error
|
var err error
|
||||||
|
|
@ -259,7 +225,7 @@ func (m *DendriteMonolith) Start() {
|
||||||
pk = sk.Public().(ed25519.PublicKey)
|
pk = sk.Public().(ed25519.PublicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.listener, err = net.Listen("tcp", "localhost:65432")
|
m.listener, err = net.Listen("tcp", ":65432")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -270,10 +236,10 @@ func (m *DendriteMonolith) Start() {
|
||||||
m.logger.SetOutput(BindLogger{})
|
m.logger.SetOutput(BindLogger{})
|
||||||
logrus.SetOutput(BindLogger{})
|
logrus.SetOutput(BindLogger{})
|
||||||
|
|
||||||
logger := log.New(os.Stdout, "PINECONE: ", 0)
|
m.PineconeRouter = pineconeRouter.NewRouter(logrus.WithField("pinecone", "router"), sk, false)
|
||||||
m.PineconeRouter = pineconeRouter.NewRouter(logger, sk, false)
|
m.PineconeQUIC = pineconeSessions.NewSessions(logrus.WithField("pinecone", "sessions"), m.PineconeRouter, []string{"matrix"})
|
||||||
m.PineconeQUIC = pineconeSessions.NewSessions(logger, m.PineconeRouter)
|
m.PineconeMulticast = pineconeMulticast.NewMulticast(logrus.WithField("pinecone", "multicast"), m.PineconeRouter)
|
||||||
m.PineconeMulticast = pineconeMulticast.NewMulticast(logger, m.PineconeRouter)
|
m.PineconeManager = pineconeConnections.NewConnectionManager(m.PineconeRouter)
|
||||||
|
|
||||||
prefix := hex.EncodeToString(pk)
|
prefix := hex.EncodeToString(pk)
|
||||||
cfg := &config.Dendrite{}
|
cfg := &config.Dendrite{}
|
||||||
|
|
@ -281,6 +247,7 @@ func (m *DendriteMonolith) Start() {
|
||||||
cfg.Global.ServerName = gomatrixserverlib.ServerName(hex.EncodeToString(pk))
|
cfg.Global.ServerName = gomatrixserverlib.ServerName(hex.EncodeToString(pk))
|
||||||
cfg.Global.PrivateKey = sk
|
cfg.Global.PrivateKey = sk
|
||||||
cfg.Global.KeyID = gomatrixserverlib.KeyID(signing.KeyID)
|
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.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.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))
|
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())
|
m.userAPI = userapi.NewInternalAPI(base, accountDB, &cfg.UserAPI, cfg.Derived.ApplicationServices, keyAPI, rsAPI, base.PushGatewayHTTPClient())
|
||||||
keyAPI.SetUserAPI(m.userAPI)
|
keyAPI.SetUserAPI(m.userAPI)
|
||||||
|
|
||||||
eduInputAPI := eduserver.NewInternalAPI(
|
|
||||||
base, cache.New(), m.userAPI,
|
|
||||||
)
|
|
||||||
|
|
||||||
asAPI := appservice.NewInternalAPI(base, m.userAPI, rsAPI)
|
asAPI := appservice.NewInternalAPI(base, m.userAPI, rsAPI)
|
||||||
|
|
||||||
// The underlying roomserver implementation needs to be able to call the fedsender.
|
// 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
|
// This is different to rsAPI which can be the http client which doesn't need this dependency
|
||||||
rsAPI.SetFederationAPI(fsAPI, keyRing)
|
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{
|
monolith := setup.Monolith{
|
||||||
Config: base.Cfg,
|
Config: base.Cfg,
|
||||||
AccountDB: accountDB,
|
AccountDB: accountDB,
|
||||||
|
|
@ -332,13 +298,13 @@ func (m *DendriteMonolith) Start() {
|
||||||
FedClient: federation,
|
FedClient: federation,
|
||||||
KeyRing: keyRing,
|
KeyRing: keyRing,
|
||||||
|
|
||||||
AppserviceAPI: asAPI,
|
AppserviceAPI: asAPI,
|
||||||
EDUInternalAPI: eduInputAPI,
|
FederationAPI: fsAPI,
|
||||||
FederationAPI: fsAPI,
|
RoomserverAPI: rsAPI,
|
||||||
RoomserverAPI: rsAPI,
|
UserAPI: m.userAPI,
|
||||||
UserAPI: m.userAPI,
|
KeyAPI: keyAPI,
|
||||||
KeyAPI: keyAPI,
|
ExtPublicRoomsProvider: roomProvider,
|
||||||
ExtPublicRoomsProvider: rooms.NewPineconeRoomProvider(m.PineconeRouter, m.PineconeQUIC, fsAPI, federation),
|
ExtUserDirectoryProvider: userProvider,
|
||||||
}
|
}
|
||||||
monolith.AddAllPublicRoutes(
|
monolith.AddAllPublicRoutes(
|
||||||
base.ProcessContext,
|
base.ProcessContext,
|
||||||
|
|
@ -354,12 +320,15 @@ func (m *DendriteMonolith) Start() {
|
||||||
httpRouter.PathPrefix(httputil.InternalPathPrefix).Handler(base.InternalAPIMux)
|
httpRouter.PathPrefix(httputil.InternalPathPrefix).Handler(base.InternalAPIMux)
|
||||||
httpRouter.PathPrefix(httputil.PublicClientPathPrefix).Handler(base.PublicClientAPIMux)
|
httpRouter.PathPrefix(httputil.PublicClientPathPrefix).Handler(base.PublicClientAPIMux)
|
||||||
httpRouter.PathPrefix(httputil.PublicMediaPathPrefix).Handler(base.PublicMediaAPIMux)
|
httpRouter.PathPrefix(httputil.PublicMediaPathPrefix).Handler(base.PublicMediaAPIMux)
|
||||||
|
httpRouter.HandleFunc("/pinecone", m.PineconeRouter.ManholeHandler)
|
||||||
|
|
||||||
pMux := mux.NewRouter().SkipClean(true).UseEncodedPath()
|
pMux := mux.NewRouter().SkipClean(true).UseEncodedPath()
|
||||||
|
pMux.PathPrefix(users.PublicURL).HandlerFunc(userProvider.FederatedUserProfiles)
|
||||||
pMux.PathPrefix(httputil.PublicFederationPathPrefix).Handler(base.PublicFederationAPIMux)
|
pMux.PathPrefix(httputil.PublicFederationPathPrefix).Handler(base.PublicFederationAPIMux)
|
||||||
pMux.PathPrefix(httputil.PublicMediaPathPrefix).Handler(base.PublicMediaAPIMux)
|
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.PublicFederationPathPrefix, pMux)
|
||||||
pHTTP.Mux().Handle(httputil.PublicMediaPathPrefix, pMux)
|
pHTTP.Mux().Handle(httputil.PublicMediaPathPrefix, pMux)
|
||||||
|
|
||||||
|
|
@ -379,33 +348,23 @@ func (m *DendriteMonolith) Start() {
|
||||||
|
|
||||||
m.processContext = base.ProcessContext
|
m.processContext = base.ProcessContext
|
||||||
|
|
||||||
m.staticPeerAttempt = make(chan struct{}, 1)
|
|
||||||
go m.staticPeerConnect()
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
m.logger.Info("Listening on ", cfg.Global.ServerName)
|
m.logger.Info("Listening on ", cfg.Global.ServerName)
|
||||||
m.logger.Fatal(m.httpServer.Serve(m.PineconeQUIC))
|
m.logger.Fatal(m.httpServer.Serve(m.PineconeQUIC.Protocol("matrix")))
|
||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
logrus.Info("Listening on ", m.listener.Addr())
|
logrus.Info("Listening on ", m.listener.Addr())
|
||||||
logrus.Fatal(http.Serve(m.listener, httpRouter))
|
logrus.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() {
|
func (m *DendriteMonolith) Stop() {
|
||||||
|
m.processContext.ShutdownDendrite()
|
||||||
_ = m.listener.Close()
|
_ = m.listener.Close()
|
||||||
m.PineconeMulticast.Stop()
|
m.PineconeMulticast.Stop()
|
||||||
_ = m.PineconeQUIC.Close()
|
_ = m.PineconeQUIC.Close()
|
||||||
m.processContext.ShutdownDendrite()
|
|
||||||
_ = m.PineconeRouter.Close()
|
_ = m.PineconeRouter.Close()
|
||||||
|
m.processContext.WaitForComponentsToFinish()
|
||||||
}
|
}
|
||||||
|
|
||||||
const MaxFrameSize = types.MaxFrameSize
|
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
|
//go:build ios
|
||||||
// +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
|
//go:build !ios
|
||||||
// +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/signing"
|
||||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/yggconn"
|
"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/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"
|
||||||
"github.com/matrix-org/dendrite/federationapi/api"
|
"github.com/matrix-org/dendrite/federationapi/api"
|
||||||
"github.com/matrix-org/dendrite/internal/httputil"
|
"github.com/matrix-org/dendrite/internal/httputil"
|
||||||
|
|
@ -23,6 +21,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/setup"
|
"github.com/matrix-org/dendrite/setup"
|
||||||
"github.com/matrix-org/dendrite/setup/base"
|
"github.com/matrix-org/dendrite/setup/base"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
|
"github.com/matrix-org/dendrite/setup/process"
|
||||||
"github.com/matrix-org/dendrite/userapi"
|
"github.com/matrix-org/dendrite/userapi"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
@ -36,6 +35,7 @@ type DendriteMonolith struct {
|
||||||
StorageDirectory string
|
StorageDirectory string
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
httpServer *http.Server
|
httpServer *http.Server
|
||||||
|
processContext *process.ProcessContext
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DendriteMonolith) BaseURL() string {
|
func (m *DendriteMonolith) BaseURL() string {
|
||||||
|
|
@ -87,6 +87,7 @@ func (m *DendriteMonolith) Start() {
|
||||||
cfg.Global.PrivateKey = ygg.PrivateKey()
|
cfg.Global.PrivateKey = ygg.PrivateKey()
|
||||||
cfg.Global.KeyID = gomatrixserverlib.KeyID(signing.KeyID)
|
cfg.Global.KeyID = gomatrixserverlib.KeyID(signing.KeyID)
|
||||||
cfg.Global.JetStream.StoragePath = config.Path(fmt.Sprintf("%s/", m.StorageDirectory))
|
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.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.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))
|
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")
|
base := base.NewBaseDendrite(cfg, "Monolith")
|
||||||
|
m.processContext = base.ProcessContext
|
||||||
defer base.Close() // nolint: errcheck
|
defer base.Close() // nolint: errcheck
|
||||||
|
|
||||||
accountDB := base.CreateAccountsDB()
|
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())
|
userAPI := userapi.NewInternalAPI(base, accountDB, &cfg.UserAPI, cfg.Derived.ApplicationServices, keyAPI, rsAPI, base.PushGatewayHTTPClient())
|
||||||
keyAPI.SetUserAPI(userAPI)
|
keyAPI.SetUserAPI(userAPI)
|
||||||
|
|
||||||
eduInputAPI := eduserver.NewInternalAPI(
|
|
||||||
base, cache.New(), userAPI,
|
|
||||||
)
|
|
||||||
|
|
||||||
asAPI := appservice.NewInternalAPI(base, userAPI, rsAPI)
|
asAPI := appservice.NewInternalAPI(base, userAPI, rsAPI)
|
||||||
rsAPI.SetAppserviceAPI(asAPI)
|
rsAPI.SetAppserviceAPI(asAPI)
|
||||||
|
|
||||||
|
|
@ -137,12 +135,11 @@ func (m *DendriteMonolith) Start() {
|
||||||
FedClient: federation,
|
FedClient: federation,
|
||||||
KeyRing: keyRing,
|
KeyRing: keyRing,
|
||||||
|
|
||||||
AppserviceAPI: asAPI,
|
AppserviceAPI: asAPI,
|
||||||
EDUInternalAPI: eduInputAPI,
|
FederationAPI: fsAPI,
|
||||||
FederationAPI: fsAPI,
|
RoomserverAPI: rsAPI,
|
||||||
RoomserverAPI: rsAPI,
|
UserAPI: userAPI,
|
||||||
UserAPI: userAPI,
|
KeyAPI: keyAPI,
|
||||||
KeyAPI: keyAPI,
|
|
||||||
ExtPublicRoomsProvider: yggrooms.NewYggdrasilRoomProvider(
|
ExtPublicRoomsProvider: yggrooms.NewYggdrasilRoomProvider(
|
||||||
ygg, fsAPI, federation,
|
ygg, fsAPI, federation,
|
||||||
),
|
),
|
||||||
|
|
@ -197,9 +194,12 @@ func (m *DendriteMonolith) Start() {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DendriteMonolith) Suspend() {
|
func (m *DendriteMonolith) Stop() {
|
||||||
m.logger.Info("Suspending monolith")
|
|
||||||
if err := m.httpServer.Close(); err != nil {
|
if err := m.httpServer.Close(); err != nil {
|
||||||
m.logger.Warn("Error stopping HTTP server:", err)
|
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.
|
// Profile represents the profile for a Matrix account.
|
||||||
type Profile struct {
|
type Profile struct {
|
||||||
Localpart string `json:"local_part"`
|
Localpart string `json:"local_part"`
|
||||||
|
ServerName string `json:"server_name,omitempty"` // NOTSPEC: only set by Pinecone user provider
|
||||||
DisplayName string `json:"display_name"`
|
DisplayName string `json:"display_name"`
|
||||||
AvatarURL string `json:"avatar_url"`
|
AvatarURL string `json:"avatar_url"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/api"
|
"github.com/matrix-org/dendrite/clientapi/api"
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
"github.com/matrix-org/dendrite/clientapi/routing"
|
"github.com/matrix-org/dendrite/clientapi/routing"
|
||||||
eduServerAPI "github.com/matrix-org/dendrite/eduserver/api"
|
|
||||||
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
||||||
"github.com/matrix-org/dendrite/internal/transactions"
|
"github.com/matrix-org/dendrite/internal/transactions"
|
||||||
keyserverAPI "github.com/matrix-org/dendrite/keyserver/api"
|
keyserverAPI "github.com/matrix-org/dendrite/keyserver/api"
|
||||||
|
|
@ -40,26 +39,32 @@ func AddPublicRoutes(
|
||||||
cfg *config.ClientAPI,
|
cfg *config.ClientAPI,
|
||||||
federation *gomatrixserverlib.FederationClient,
|
federation *gomatrixserverlib.FederationClient,
|
||||||
rsAPI roomserverAPI.RoomserverInternalAPI,
|
rsAPI roomserverAPI.RoomserverInternalAPI,
|
||||||
eduInputAPI eduServerAPI.EDUServerInputAPI,
|
|
||||||
asAPI appserviceAPI.AppServiceQueryAPI,
|
asAPI appserviceAPI.AppServiceQueryAPI,
|
||||||
transactionsCache *transactions.Cache,
|
transactionsCache *transactions.Cache,
|
||||||
fsAPI federationAPI.FederationInternalAPI,
|
fsAPI federationAPI.FederationInternalAPI,
|
||||||
userAPI userapi.UserInternalAPI,
|
userAPI userapi.UserInternalAPI,
|
||||||
|
userDirectoryProvider userapi.UserDirectoryProvider,
|
||||||
keyAPI keyserverAPI.KeyInternalAPI,
|
keyAPI keyserverAPI.KeyInternalAPI,
|
||||||
extRoomsProvider api.ExtraPublicRoomsProvider,
|
extRoomsProvider api.ExtraPublicRoomsProvider,
|
||||||
mscCfg *config.MSCs,
|
mscCfg *config.MSCs,
|
||||||
) {
|
) {
|
||||||
js, _ := jetstream.Prepare(process, &cfg.Matrix.JetStream)
|
js, natsClient := jetstream.Prepare(process, &cfg.Matrix.JetStream)
|
||||||
|
|
||||||
syncProducer := &producers.SyncAPIProducer{
|
syncProducer := &producers.SyncAPIProducer{
|
||||||
JetStream: js,
|
JetStream: js,
|
||||||
Topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputClientData),
|
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(
|
routing.Setup(
|
||||||
router, synapseAdminRouter, cfg, eduInputAPI, rsAPI, asAPI,
|
router, synapseAdminRouter, cfg, rsAPI, asAPI,
|
||||||
userAPI, federation,
|
userAPI, userDirectoryProvider, federation,
|
||||||
syncProducer, transactionsCache, fsAPI, keyAPI,
|
syncProducer, transactionsCache, fsAPI, keyAPI,
|
||||||
extRoomsProvider, mscCfg,
|
extRoomsProvider, mscCfg, natsClient,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,32 +15,45 @@
|
||||||
package producers
|
package producers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
"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"
|
"github.com/nats-io/nats.go"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SyncAPIProducer produces events for the sync API server to consume
|
// SyncAPIProducer produces events for the sync API server to consume
|
||||||
type SyncAPIProducer struct {
|
type SyncAPIProducer struct {
|
||||||
Topic string
|
TopicClientData string
|
||||||
JetStream nats.JetStreamContext
|
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
|
// 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{
|
m := &nats.Msg{
|
||||||
Subject: p.Topic,
|
Subject: p.TopicClientData,
|
||||||
Header: nats.Header{},
|
Header: nats.Header{},
|
||||||
}
|
}
|
||||||
m.Header.Set(jetstream.UserID, userID)
|
m.Header.Set(jetstream.UserID, userID)
|
||||||
|
|
||||||
data := eventutil.AccountData{
|
data := eventutil.AccountData{
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
Type: dataType,
|
Type: dataType,
|
||||||
ReadMarker: readMarker,
|
ReadMarker: readMarker,
|
||||||
|
IgnoredUsers: ignoredUsers,
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
m.Data, err = json.Marshal(data)
|
m.Data, err = json.Marshal(data)
|
||||||
|
|
@ -52,8 +65,130 @@ func (p *SyncAPIProducer) SendData(userID string, roomID string, dataType string
|
||||||
"user_id": userID,
|
"user_id": userID,
|
||||||
"room_id": roomID,
|
"room_id": roomID,
|
||||||
"data_type": dataType,
|
"data_type": dataType,
|
||||||
}).Tracef("Producing to topic '%s'", p.Topic)
|
}).Tracef("Producing to topic '%s'", p.TopicClientData)
|
||||||
|
|
||||||
_, err = p.JetStream.PublishMsg(m)
|
_, err = p.JetStream.PublishMsg(m)
|
||||||
return err
|
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/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
eduserverAPI "github.com/matrix-org/dendrite/eduserver/api"
|
|
||||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
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/dendrite/userapi/api"
|
||||||
|
|
||||||
"github.com/matrix-org/util"
|
"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{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
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)
|
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
|
// 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")
|
util.GetLogger(req.Context()).WithError(err).Error("syncProducer.SendData failed")
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
@ -146,7 +152,7 @@ type fullyReadEvent struct {
|
||||||
// SaveReadMarker implements POST /rooms/{roomId}/read_markers
|
// SaveReadMarker implements POST /rooms/{roomId}/read_markers
|
||||||
func SaveReadMarker(
|
func SaveReadMarker(
|
||||||
req *http.Request,
|
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,
|
syncProducer *producers.SyncAPIProducer, device *api.Device, roomID string,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
// Verify that the user is a member of this room
|
// Verify that the user is a member of this room
|
||||||
|
|
@ -185,14 +191,14 @@ func SaveReadMarker(
|
||||||
return util.ErrorResponse(err)
|
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")
|
util.GetLogger(req.Context()).WithError(err).Error("syncProducer.SendData failed")
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the read receipt that may be included in the read marker
|
// Handle the read receipt that may be included in the read marker
|
||||||
if r.Read != "" {
|
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{
|
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()
|
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")
|
util.GetLogger(req.Context()).WithError(err).Error("SendEvents failed")
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,21 +19,20 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/eduserver/api"
|
|
||||||
|
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
"github.com/sirupsen/logrus"
|
"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())
|
timestamp := gomatrixserverlib.AsTimestamp(time.Now())
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
"roomId": roomId,
|
"roomID": roomID,
|
||||||
"receiptType": receiptType,
|
"receiptType": receiptType,
|
||||||
"eventId": eventId,
|
"eventID": eventID,
|
||||||
"userId": device.UserID,
|
"userId": device.UserID,
|
||||||
"timestamp": timestamp,
|
"timestamp": timestamp,
|
||||||
}).Debug("Setting receipt")
|
}).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))
|
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)
|
return util.ErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/gomatrixserverlib/tokens"
|
"github.com/matrix-org/gomatrixserverlib/tokens"
|
||||||
|
|
@ -63,11 +64,6 @@ const (
|
||||||
sessionIDLength = 24
|
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.
|
// sessionsDict keeps track of completed auth stages for each session.
|
||||||
// It shouldn't be passed by value because it contains a mutex.
|
// It shouldn't be passed by value because it contains a mutex.
|
||||||
type sessionsDict struct {
|
type sessionsDict struct {
|
||||||
|
|
@ -525,22 +521,37 @@ func Register(
|
||||||
userAPI userapi.UserRegisterAPI,
|
userAPI userapi.UserRegisterAPI,
|
||||||
cfg *config.ClientAPI,
|
cfg *config.ClientAPI,
|
||||||
) util.JSONResponse {
|
) 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
|
var r registerRequest
|
||||||
resErr := httputil.UnmarshalJSONRequest(req, &r)
|
sessionID := gjson.GetBytes(reqBody, "auth.session").String()
|
||||||
if resErr != nil {
|
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
|
return *resErr
|
||||||
}
|
}
|
||||||
if req.URL.Query().Get("kind") == "guest" {
|
if req.URL.Query().Get("kind") == "guest" {
|
||||||
return handleGuestRegistration(req, r, cfg, userAPI)
|
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.
|
// Don't allow numeric usernames less than MAX_INT64.
|
||||||
if _, err := strconv.ParseInt(r.Username, 10, 64); err == nil {
|
if _, err := strconv.ParseInt(r.Username, 10, 64); err == nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -568,7 +579,7 @@ func Register(
|
||||||
case r.Type == authtypes.LoginTypeApplicationService && accessTokenErr == nil:
|
case r.Type == authtypes.LoginTypeApplicationService && accessTokenErr == nil:
|
||||||
// Spec-compliant case (the access_token is specified and the login type
|
// Spec-compliant case (the access_token is specified and the login type
|
||||||
// is correctly set, so it's an appservice registration)
|
// 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
|
return *resErr
|
||||||
}
|
}
|
||||||
case accessTokenErr == nil:
|
case accessTokenErr == nil:
|
||||||
|
|
@ -581,11 +592,11 @@ func Register(
|
||||||
default:
|
default:
|
||||||
// Spec-compliant case (neither the access_token nor the login type are
|
// Spec-compliant case (neither the access_token nor the login type are
|
||||||
// specified, so it's a normal user registration)
|
// specified, so it's a normal user registration)
|
||||||
if resErr = validateUsername(r.Username); resErr != nil {
|
if resErr := validateUsername(r.Username); resErr != nil {
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if resErr = validatePassword(r.Password); resErr != nil {
|
if resErr := validatePassword(r.Password); resErr != nil {
|
||||||
return *resErr
|
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 == "" {
|
if username == "" {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("missing username"),
|
JSON: jsonerror.MissingArgument("Missing username"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Blank passwords are only allowed by registered application services
|
// Blank passwords are only allowed by registered application services
|
||||||
if password == "" && appserviceID == "" {
|
if password == "" && appserviceID == "" {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("missing password"),
|
JSON: jsonerror.MissingArgument("Missing password"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var accRes userapi.PerformAccountCreationResponse
|
var accRes userapi.PerformAccountCreationResponse
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ func PutTag(
|
||||||
return jsonerror.InternalServerError()
|
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")
|
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
|
// 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")
|
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"
|
clientutil "github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
eduServerAPI "github.com/matrix-org/dendrite/eduserver/api"
|
|
||||||
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
||||||
"github.com/matrix-org/dendrite/internal/httputil"
|
"github.com/matrix-org/dendrite/internal/httputil"
|
||||||
"github.com/matrix-org/dendrite/internal/transactions"
|
"github.com/matrix-org/dendrite/internal/transactions"
|
||||||
keyserverAPI "github.com/matrix-org/dendrite/keyserver/api"
|
keyserverAPI "github.com/matrix-org/dendrite/keyserver/api"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
"github.com/nats-io/nats.go"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -47,18 +49,20 @@ import (
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
func Setup(
|
func Setup(
|
||||||
publicAPIMux, synapseAdminRouter *mux.Router, cfg *config.ClientAPI,
|
publicAPIMux, synapseAdminRouter *mux.Router, cfg *config.ClientAPI,
|
||||||
eduAPI eduServerAPI.EDUServerInputAPI,
|
|
||||||
rsAPI roomserverAPI.RoomserverInternalAPI,
|
rsAPI roomserverAPI.RoomserverInternalAPI,
|
||||||
asAPI appserviceAPI.AppServiceQueryAPI,
|
asAPI appserviceAPI.AppServiceQueryAPI,
|
||||||
userAPI userapi.UserInternalAPI,
|
userAPI userapi.UserInternalAPI,
|
||||||
|
userDirectoryProvider userapi.UserDirectoryProvider,
|
||||||
federation *gomatrixserverlib.FederationClient,
|
federation *gomatrixserverlib.FederationClient,
|
||||||
syncProducer *producers.SyncAPIProducer,
|
syncProducer *producers.SyncAPIProducer,
|
||||||
transactionsCache *transactions.Cache,
|
transactionsCache *transactions.Cache,
|
||||||
federationSender federationAPI.FederationInternalAPI,
|
federationSender federationAPI.FederationInternalAPI,
|
||||||
keyAPI keyserverAPI.KeyInternalAPI,
|
keyAPI keyserverAPI.KeyInternalAPI,
|
||||||
extRoomsProvider api.ExtraPublicRoomsProvider,
|
extRoomsProvider api.ExtraPublicRoomsProvider,
|
||||||
mscCfg *config.MSCs,
|
mscCfg *config.MSCs, natsClient *nats.Conn,
|
||||||
) {
|
) {
|
||||||
|
prometheus.MustRegister(amtRegUsers, sendEventDuration)
|
||||||
|
|
||||||
rateLimits := httputil.NewRateLimits(&cfg.RateLimiting)
|
rateLimits := httputil.NewRateLimits(&cfg.RateLimiting)
|
||||||
userInteractiveAuth := auth.NewUserInteractive(userAPI, cfg)
|
userInteractiveAuth := auth.NewUserInteractive(userAPI, cfg)
|
||||||
|
|
||||||
|
|
@ -466,7 +470,7 @@ func Setup(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.ErrorResponse(err)
|
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)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
v3mux.Handle("/rooms/{roomID}/redact/{eventID}",
|
v3mux.Handle("/rooms/{roomID}/redact/{eventID}",
|
||||||
|
|
@ -495,7 +499,7 @@ func Setup(
|
||||||
return util.ErrorResponse(err)
|
return util.ErrorResponse(err)
|
||||||
}
|
}
|
||||||
txnID := vars["txnID"]
|
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)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
|
||||||
|
|
@ -509,7 +513,7 @@ func Setup(
|
||||||
return util.ErrorResponse(err)
|
return util.ErrorResponse(err)
|
||||||
}
|
}
|
||||||
txnID := vars["txnID"]
|
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)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
|
||||||
|
|
@ -780,20 +784,6 @@ func Setup(
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).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",
|
v3mux.Handle("/voip/turnServer",
|
||||||
httputil.MakeAuthAPI("turn_server", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("turn_server", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
if r := rateLimits.Limit(req); r != nil {
|
if r := rateLimits.Limit(req); r != nil {
|
||||||
|
|
@ -904,6 +894,7 @@ func Setup(
|
||||||
device,
|
device,
|
||||||
userAPI,
|
userAPI,
|
||||||
rsAPI,
|
rsAPI,
|
||||||
|
userDirectoryProvider,
|
||||||
cfg.Matrix.ServerName,
|
cfg.Matrix.ServerName,
|
||||||
postContent.SearchString,
|
postContent.SearchString,
|
||||||
postContent.Limit,
|
postContent.Limit,
|
||||||
|
|
@ -940,7 +931,7 @@ func Setup(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.ErrorResponse(err)
|
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)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
|
||||||
|
|
@ -957,6 +948,16 @@ func Setup(
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).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",
|
v3mux.Handle("/devices",
|
||||||
httputil.MakeAuthAPI("get_devices", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("get_devices", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
return GetDevicesByLocalpart(req, userAPI, device)
|
return GetDevicesByLocalpart(req, userAPI, device)
|
||||||
|
|
@ -1295,7 +1296,25 @@ func Setup(
|
||||||
return util.ErrorResponse(err)
|
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)
|
).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
|
userRoomSendMutexes sync.Map // (roomID+userID) -> mutex. mutexes to ensure correct ordering of sendEvents
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
prometheus.MustRegister(sendEventDuration)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sendEventDuration = prometheus.NewHistogramVec(
|
var sendEventDuration = prometheus.NewHistogramVec(
|
||||||
prometheus.HistogramOpts{
|
prometheus.HistogramOpts{
|
||||||
Namespace: "dendrite",
|
Namespace: "dendrite",
|
||||||
|
|
@ -272,5 +268,24 @@ func generateSendEvent(
|
||||||
JSON: jsonerror.Forbidden(err.Error()), // TODO: Is this error string comprehensible to the client?
|
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
|
return e.Event, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,17 +18,17 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"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"
|
"github.com/matrix-org/dendrite/internal/transactions"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SendToDevice handles PUT /_matrix/client/r0/sendToDevice/{eventType}/{txnId}
|
// 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(
|
func SendToDevice(
|
||||||
req *http.Request, device *userapi.Device,
|
req *http.Request, device *userapi.Device,
|
||||||
eduAPI api.EDUServerInputAPI,
|
syncProducer *producers.SyncAPIProducer,
|
||||||
txnCache *transactions.Cache,
|
txnCache *transactions.Cache,
|
||||||
eventType string, txnID *string,
|
eventType string, txnID *string,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
|
|
@ -48,8 +48,8 @@ func SendToDevice(
|
||||||
|
|
||||||
for userID, byUser := range httpReq.Messages {
|
for userID, byUser := range httpReq.Messages {
|
||||||
for deviceID, message := range byUser {
|
for deviceID, message := range byUser {
|
||||||
if err := api.SendToDevice(
|
if err := syncProducer.SendToDevice(
|
||||||
req.Context(), eduAPI, device.UserID, userID, deviceID, eventType, message,
|
req.Context(), device.UserID, userID, deviceID, eventType, message,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("eduProducer.SendToDevice failed")
|
util.GetLogger(req.Context()).WithError(err).Error("eduProducer.SendToDevice failed")
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError()
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"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"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
@ -32,9 +32,8 @@ type typingContentJSON struct {
|
||||||
// sends the typing events to client API typingProducer
|
// sends the typing events to client API typingProducer
|
||||||
func SendTyping(
|
func SendTyping(
|
||||||
req *http.Request, device *userapi.Device, roomID string,
|
req *http.Request, device *userapi.Device, roomID string,
|
||||||
userID string,
|
userID string, rsAPI roomserverAPI.RoomserverInternalAPI,
|
||||||
eduAPI api.EDUServerInputAPI,
|
syncProducer *producers.SyncAPIProducer,
|
||||||
rsAPI roomserverAPI.RoomserverInternalAPI,
|
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
if device.UserID != userID {
|
if device.UserID != userID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -56,9 +55,7 @@ func SendTyping(
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := api.SendTyping(
|
if err := syncProducer.SendTyping(req.Context(), userID, roomID, r.Typing, r.Timeout); err != nil {
|
||||||
req.Context(), eduAPI, userID, roomID, r.Typing, r.Timeout,
|
|
||||||
); err != nil {
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("eduProducer.Send failed")
|
util.GetLogger(req.Context()).WithError(err).Error("eduProducer.Send failed")
|
||||||
return jsonerror.InternalServerError()
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
|
|
@ -35,6 +36,7 @@ func SearchUserDirectory(
|
||||||
device *userapi.Device,
|
device *userapi.Device,
|
||||||
userAPI userapi.UserInternalAPI,
|
userAPI userapi.UserInternalAPI,
|
||||||
rsAPI api.RoomserverInternalAPI,
|
rsAPI api.RoomserverInternalAPI,
|
||||||
|
provider userapi.UserDirectoryProvider,
|
||||||
serverName gomatrixserverlib.ServerName,
|
serverName gomatrixserverlib.ServerName,
|
||||||
searchString string,
|
searchString string,
|
||||||
limit int,
|
limit int,
|
||||||
|
|
@ -50,13 +52,12 @@ func SearchUserDirectory(
|
||||||
}
|
}
|
||||||
|
|
||||||
// First start searching local users.
|
// First start searching local users.
|
||||||
|
|
||||||
userReq := &userapi.QuerySearchProfilesRequest{
|
userReq := &userapi.QuerySearchProfilesRequest{
|
||||||
SearchString: searchString,
|
SearchString: searchString,
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
}
|
}
|
||||||
userRes := &userapi.QuerySearchProfilesResponse{}
|
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))
|
errRes := util.ErrorResponse(fmt.Errorf("userAPI.QuerySearchProfiles: %w", err))
|
||||||
return &errRes
|
return &errRes
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +68,12 @@ func SearchUserDirectory(
|
||||||
break
|
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 {
|
if _, ok := results[userID]; !ok {
|
||||||
results[userID] = authtypes.FullyQualifiedProfile{
|
results[userID] = authtypes.FullyQualifiedProfile{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
|
|
@ -87,7 +93,7 @@ func SearchUserDirectory(
|
||||||
Limit: limit - len(results),
|
Limit: limit - len(results),
|
||||||
}
|
}
|
||||||
stateRes := &api.QueryKnownUsersResponse{}
|
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))
|
errRes := util.ErrorResponse(fmt.Errorf("rsAPI.QueryKnownUsers: %w", err))
|
||||||
return &errRes
|
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"
|
p2pdisc "github.com/libp2p/go-libp2p/p2p/discovery"
|
||||||
"github.com/matrix-org/dendrite/appservice"
|
"github.com/matrix-org/dendrite/appservice"
|
||||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/embed"
|
"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/federationapi"
|
||||||
"github.com/matrix-org/dendrite/internal/httputil"
|
"github.com/matrix-org/dendrite/internal/httputil"
|
||||||
"github.com/matrix-org/dendrite/keyserver"
|
"github.com/matrix-org/dendrite/keyserver"
|
||||||
|
|
@ -40,8 +39,6 @@ import (
|
||||||
"github.com/matrix-org/dendrite/userapi"
|
"github.com/matrix-org/dendrite/userapi"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/eduserver/cache"
|
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "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())
|
userAPI := userapi.NewInternalAPI(&base.Base, accountDB, &cfg.UserAPI, nil, keyAPI, rsAPI, base.Base.PushGatewayHTTPClient())
|
||||||
keyAPI.SetUserAPI(userAPI)
|
keyAPI.SetUserAPI(userAPI)
|
||||||
|
|
||||||
eduInputAPI := eduserver.NewInternalAPI(
|
|
||||||
&base.Base, cache.New(), userAPI,
|
|
||||||
)
|
|
||||||
asAPI := appservice.NewInternalAPI(&base.Base, userAPI, rsAPI)
|
asAPI := appservice.NewInternalAPI(&base.Base, userAPI, rsAPI)
|
||||||
rsAPI.SetAppserviceAPI(asAPI)
|
rsAPI.SetAppserviceAPI(asAPI)
|
||||||
fsAPI := federationapi.NewInternalAPI(
|
fsAPI := federationapi.NewInternalAPI(
|
||||||
|
|
@ -180,7 +174,6 @@ func main() {
|
||||||
KeyRing: keyRing,
|
KeyRing: keyRing,
|
||||||
|
|
||||||
AppserviceAPI: asAPI,
|
AppserviceAPI: asAPI,
|
||||||
EDUInternalAPI: eduInputAPI,
|
|
||||||
FederationAPI: fsAPI,
|
FederationAPI: fsAPI,
|
||||||
RoomserverAPI: rsAPI,
|
RoomserverAPI: rsAPI,
|
||||||
UserAPI: userAPI,
|
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
|
package conn
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -53,21 +67,22 @@ func (y *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTransport(s *pineconeSessions.Sessions) *http.Transport {
|
func createTransport(s *pineconeSessions.Sessions) *http.Transport {
|
||||||
|
proto := s.Protocol("matrix")
|
||||||
tr := &http.Transport{
|
tr := &http.Transport{
|
||||||
DisableKeepAlives: false,
|
DisableKeepAlives: false,
|
||||||
Dial: s.Dial,
|
Dial: proto.Dial,
|
||||||
DialContext: s.DialContext,
|
DialContext: proto.DialContext,
|
||||||
DialTLS: s.DialTLS,
|
DialTLS: proto.DialTLS,
|
||||||
DialTLSContext: s.DialTLSContext,
|
DialTLSContext: proto.DialTLSContext,
|
||||||
}
|
}
|
||||||
tr.RegisterProtocol(
|
tr.RegisterProtocol(
|
||||||
"matrix", &RoundTripper{
|
"matrix", &RoundTripper{
|
||||||
inner: &http.Transport{
|
inner: &http.Transport{
|
||||||
DisableKeepAlives: false,
|
DisableKeepAlives: false,
|
||||||
Dial: s.Dial,
|
Dial: proto.Dial,
|
||||||
DialContext: s.DialContext,
|
DialContext: proto.DialContext,
|
||||||
DialTLS: s.DialTLS,
|
DialTLS: proto.DialTLS,
|
||||||
DialTLSContext: s.DialTLSContext,
|
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
|
package conn
|
||||||
|
|
||||||
import (
|
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
|
//go:build elementweb
|
||||||
// +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
|
//go:build !elementweb
|
||||||
// +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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -22,11 +22,9 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"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/conn"
|
||||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/embed"
|
"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/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/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"
|
||||||
"github.com/matrix-org/dendrite/federationapi/api"
|
|
||||||
"github.com/matrix-org/dendrite/internal"
|
"github.com/matrix-org/dendrite/internal"
|
||||||
"github.com/matrix-org/dendrite/internal/httputil"
|
"github.com/matrix-org/dendrite/internal/httputil"
|
||||||
"github.com/matrix-org/dendrite/keyserver"
|
"github.com/matrix-org/dendrite/keyserver"
|
||||||
|
|
@ -50,6 +46,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/userapi"
|
"github.com/matrix-org/dendrite/userapi"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
||||||
|
pineconeConnections "github.com/matrix-org/pinecone/connections"
|
||||||
pineconeMulticast "github.com/matrix-org/pinecone/multicast"
|
pineconeMulticast "github.com/matrix-org/pinecone/multicast"
|
||||||
pineconeRouter "github.com/matrix-org/pinecone/router"
|
pineconeRouter "github.com/matrix-org/pinecone/router"
|
||||||
pineconeSessions "github.com/matrix-org/pinecone/sessions"
|
pineconeSessions "github.com/matrix-org/pinecone/sessions"
|
||||||
|
|
@ -92,8 +89,14 @@ func main() {
|
||||||
pk = sk.Public().(ed25519.PublicKey)
|
pk = sk.Public().(ed25519.PublicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := log.New(os.Stdout, "", 0)
|
pRouter := pineconeRouter.NewRouter(logrus.WithField("pinecone", "router"), sk, false)
|
||||||
pRouter := pineconeRouter.NewRouter(logger, 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() {
|
go func() {
|
||||||
listener, err := net.Listen("tcp", *instanceListen)
|
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 := &config.Dendrite{}
|
||||||
cfg.Defaults(true)
|
cfg.Defaults(true)
|
||||||
cfg.Global.ServerName = gomatrixserverlib.ServerName(hex.EncodeToString(pk))
|
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())
|
userAPI := userapi.NewInternalAPI(base, accountDB, &cfg.UserAPI, nil, keyAPI, rsAPI, base.PushGatewayHTTPClient())
|
||||||
keyAPI.SetUserAPI(userAPI)
|
keyAPI.SetUserAPI(userAPI)
|
||||||
|
|
||||||
eduInputAPI := eduserver.NewInternalAPI(
|
|
||||||
base, cache.New(), userAPI,
|
|
||||||
)
|
|
||||||
|
|
||||||
asAPI := appservice.NewInternalAPI(base, userAPI, rsAPI)
|
asAPI := appservice.NewInternalAPI(base, userAPI, rsAPI)
|
||||||
|
|
||||||
rsComponent.SetFederationAPI(fsAPI, keyRing)
|
rsComponent.SetFederationAPI(fsAPI, keyRing)
|
||||||
|
|
||||||
|
userProvider := users.NewPineconeUserProvider(pRouter, pQUIC, userAPI, federation)
|
||||||
|
roomProvider := rooms.NewPineconeRoomProvider(pRouter, pQUIC, fsAPI, federation)
|
||||||
|
|
||||||
monolith := setup.Monolith{
|
monolith := setup.Monolith{
|
||||||
Config: base.Cfg,
|
Config: base.Cfg,
|
||||||
AccountDB: accountDB,
|
AccountDB: accountDB,
|
||||||
|
|
@ -205,13 +177,13 @@ func main() {
|
||||||
FedClient: federation,
|
FedClient: federation,
|
||||||
KeyRing: keyRing,
|
KeyRing: keyRing,
|
||||||
|
|
||||||
AppserviceAPI: asAPI,
|
AppserviceAPI: asAPI,
|
||||||
EDUInternalAPI: eduInputAPI,
|
FederationAPI: fsAPI,
|
||||||
FederationAPI: fsAPI,
|
RoomserverAPI: rsAPI,
|
||||||
RoomserverAPI: rsAPI,
|
UserAPI: userAPI,
|
||||||
UserAPI: userAPI,
|
KeyAPI: keyAPI,
|
||||||
KeyAPI: keyAPI,
|
ExtPublicRoomsProvider: roomProvider,
|
||||||
ExtPublicRoomsProvider: rooms.NewPineconeRoomProvider(pRouter, pQUIC, fsAPI, federation),
|
ExtUserDirectoryProvider: userProvider,
|
||||||
}
|
}
|
||||||
monolith.AddAllPublicRoutes(
|
monolith.AddAllPublicRoutes(
|
||||||
base.ProcessContext,
|
base.ProcessContext,
|
||||||
|
|
@ -247,13 +219,16 @@ func main() {
|
||||||
logrus.WithError(err).Error("Failed to connect WebSocket peer to Pinecone switch")
|
logrus.WithError(err).Error("Failed to connect WebSocket peer to Pinecone switch")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
httpRouter.HandleFunc("/pinecone", pRouter.ManholeHandler)
|
||||||
embed.Embed(httpRouter, *instancePort, "Pinecone Demo")
|
embed.Embed(httpRouter, *instancePort, "Pinecone Demo")
|
||||||
|
|
||||||
pMux := mux.NewRouter().SkipClean(true).UseEncodedPath()
|
pMux := mux.NewRouter().SkipClean(true).UseEncodedPath()
|
||||||
|
pMux.PathPrefix(users.PublicURL).HandlerFunc(userProvider.FederatedUserProfiles)
|
||||||
pMux.PathPrefix(httputil.PublicFederationPathPrefix).Handler(base.PublicFederationAPIMux)
|
pMux.PathPrefix(httputil.PublicFederationPathPrefix).Handler(base.PublicFederationAPIMux)
|
||||||
pMux.PathPrefix(httputil.PublicMediaPathPrefix).Handler(base.PublicMediaAPIMux)
|
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.PublicFederationPathPrefix, pMux)
|
||||||
pHTTP.Mux().Handle(httputil.PublicMediaPathPrefix, pMux)
|
pHTTP.Mux().Handle(httputil.PublicMediaPathPrefix, pMux)
|
||||||
|
|
||||||
|
|
@ -270,25 +245,16 @@ func main() {
|
||||||
Handler: pMux,
|
Handler: pMux,
|
||||||
}
|
}
|
||||||
|
|
||||||
go connectToStaticPeer()
|
|
||||||
go func() {
|
go func() {
|
||||||
pubkey := pRouter.PublicKey()
|
pubkey := pRouter.PublicKey()
|
||||||
logrus.Info("Listening on ", hex.EncodeToString(pubkey[:]))
|
logrus.Info("Listening on ", hex.EncodeToString(pubkey[:]))
|
||||||
logrus.Fatal(httpServer.Serve(pQUIC))
|
logrus.Fatal(httpServer.Serve(pQUIC.Protocol("matrix")))
|
||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
httpBindAddr := fmt.Sprintf(":%d", *instancePort)
|
httpBindAddr := fmt.Sprintf(":%d", *instancePort)
|
||||||
logrus.Info("Listening on ", httpBindAddr)
|
logrus.Info("Listening on ", httpBindAddr)
|
||||||
logrus.Fatal(http.ListenAndServe(httpBindAddr, httpRouter))
|
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()
|
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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/defaults"
|
||||||
"github.com/matrix-org/dendrite/federationapi/api"
|
"github.com/matrix-org/dendrite/federationapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
@ -50,9 +51,12 @@ func NewPineconeRoomProvider(
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PineconeRoomProvider) Rooms() []gomatrixserverlib.PublicRoom {
|
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() {
|
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)
|
return bulkFetchPublicRoomsFromServers(context.Background(), p.fedClient, list)
|
||||||
}
|
}
|
||||||
|
|
@ -61,7 +65,7 @@ func (p *PineconeRoomProvider) Rooms() []gomatrixserverlib.PublicRoom {
|
||||||
// Returns a list of public rooms.
|
// Returns a list of public rooms.
|
||||||
func bulkFetchPublicRoomsFromServers(
|
func bulkFetchPublicRoomsFromServers(
|
||||||
ctx context.Context, fedClient *gomatrixserverlib.FederationClient,
|
ctx context.Context, fedClient *gomatrixserverlib.FederationClient,
|
||||||
homeservers []gomatrixserverlib.ServerName,
|
homeservers map[gomatrixserverlib.ServerName]struct{},
|
||||||
) (publicRooms []gomatrixserverlib.PublicRoom) {
|
) (publicRooms []gomatrixserverlib.PublicRoom) {
|
||||||
limit := 200
|
limit := 200
|
||||||
// follow pipeline semantics, see https://blog.golang.org/pipelines for more info.
|
// follow pipeline semantics, see https://blog.golang.org/pipelines for more info.
|
||||||
|
|
@ -74,7 +78,7 @@ func bulkFetchPublicRoomsFromServers(
|
||||||
wg.Add(len(homeservers))
|
wg.Add(len(homeservers))
|
||||||
// concurrently query for public rooms
|
// concurrently query for public rooms
|
||||||
reqctx, reqcancel := context.WithTimeout(ctx, time.Second*5)
|
reqctx, reqcancel := context.WithTimeout(ctx, time.Second*5)
|
||||||
for _, hs := range homeservers {
|
for hs := range homeservers {
|
||||||
go func(homeserverDomain gomatrixserverlib.ServerName) {
|
go func(homeserverDomain gomatrixserverlib.ServerName) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
util.GetLogger(reqctx).WithField("hs", homeserverDomain).Info("Querying HS for public rooms")
|
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/signing"
|
||||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/yggconn"
|
"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/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"
|
||||||
"github.com/matrix-org/dendrite/federationapi/api"
|
"github.com/matrix-org/dendrite/federationapi/api"
|
||||||
"github.com/matrix-org/dendrite/internal"
|
"github.com/matrix-org/dendrite/internal"
|
||||||
|
|
@ -120,10 +118,6 @@ func main() {
|
||||||
userAPI := userapi.NewInternalAPI(base, accountDB, &cfg.UserAPI, nil, keyAPI, rsAPI, base.PushGatewayHTTPClient())
|
userAPI := userapi.NewInternalAPI(base, accountDB, &cfg.UserAPI, nil, keyAPI, rsAPI, base.PushGatewayHTTPClient())
|
||||||
keyAPI.SetUserAPI(userAPI)
|
keyAPI.SetUserAPI(userAPI)
|
||||||
|
|
||||||
eduInputAPI := eduserver.NewInternalAPI(
|
|
||||||
base, cache.New(), userAPI,
|
|
||||||
)
|
|
||||||
|
|
||||||
asAPI := appservice.NewInternalAPI(base, userAPI, rsAPI)
|
asAPI := appservice.NewInternalAPI(base, userAPI, rsAPI)
|
||||||
rsAPI.SetAppserviceAPI(asAPI)
|
rsAPI.SetAppserviceAPI(asAPI)
|
||||||
fsAPI := federationapi.NewInternalAPI(
|
fsAPI := federationapi.NewInternalAPI(
|
||||||
|
|
@ -139,12 +133,11 @@ func main() {
|
||||||
FedClient: federation,
|
FedClient: federation,
|
||||||
KeyRing: keyRing,
|
KeyRing: keyRing,
|
||||||
|
|
||||||
AppserviceAPI: asAPI,
|
AppserviceAPI: asAPI,
|
||||||
EDUInternalAPI: eduInputAPI,
|
FederationAPI: fsAPI,
|
||||||
FederationAPI: fsAPI,
|
RoomserverAPI: rsAPI,
|
||||||
RoomserverAPI: rsAPI,
|
UserAPI: userAPI,
|
||||||
UserAPI: userAPI,
|
KeyAPI: keyAPI,
|
||||||
KeyAPI: keyAPI,
|
|
||||||
ExtPublicRoomsProvider: yggrooms.NewYggdrasilRoomProvider(
|
ExtPublicRoomsProvider: yggrooms.NewYggdrasilRoomProvider(
|
||||||
ygg, fsAPI, federation,
|
ygg, fsAPI, federation,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/appservice"
|
"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/federationapi"
|
||||||
"github.com/matrix-org/dendrite/keyserver"
|
"github.com/matrix-org/dendrite/keyserver"
|
||||||
"github.com/matrix-org/dendrite/roomserver"
|
"github.com/matrix-org/dendrite/roomserver"
|
||||||
|
|
@ -61,7 +59,6 @@ func main() {
|
||||||
// itself.
|
// itself.
|
||||||
cfg.AppServiceAPI.InternalAPI.Connect = httpAPIAddr
|
cfg.AppServiceAPI.InternalAPI.Connect = httpAPIAddr
|
||||||
cfg.ClientAPI.InternalAPI.Connect = httpAPIAddr
|
cfg.ClientAPI.InternalAPI.Connect = httpAPIAddr
|
||||||
cfg.EDUServer.InternalAPI.Connect = httpAPIAddr
|
|
||||||
cfg.FederationAPI.InternalAPI.Connect = httpAPIAddr
|
cfg.FederationAPI.InternalAPI.Connect = httpAPIAddr
|
||||||
cfg.KeyServer.InternalAPI.Connect = httpAPIAddr
|
cfg.KeyServer.InternalAPI.Connect = httpAPIAddr
|
||||||
cfg.MediaAPI.InternalAPI.Connect = httpAPIAddr
|
cfg.MediaAPI.InternalAPI.Connect = httpAPIAddr
|
||||||
|
|
@ -136,14 +133,6 @@ func main() {
|
||||||
rsImpl.SetUserAPI(userAPI)
|
rsImpl.SetUserAPI(userAPI)
|
||||||
keyImpl.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{
|
monolith := setup.Monolith{
|
||||||
Config: base.Cfg,
|
Config: base.Cfg,
|
||||||
AccountDB: accountDB,
|
AccountDB: accountDB,
|
||||||
|
|
@ -151,12 +140,10 @@ func main() {
|
||||||
FedClient: federation,
|
FedClient: federation,
|
||||||
KeyRing: keyRing,
|
KeyRing: keyRing,
|
||||||
|
|
||||||
AppserviceAPI: asAPI,
|
AppserviceAPI: asAPI, FederationAPI: fsAPI,
|
||||||
EDUInternalAPI: eduInputAPI,
|
RoomserverAPI: rsAPI,
|
||||||
FederationAPI: fsAPI,
|
UserAPI: userAPI,
|
||||||
RoomserverAPI: rsAPI,
|
KeyAPI: keyAPI,
|
||||||
UserAPI: userAPI,
|
|
||||||
KeyAPI: keyAPI,
|
|
||||||
}
|
}
|
||||||
monolith.AddAllPublicRoutes(
|
monolith.AddAllPublicRoutes(
|
||||||
base.ProcessContext,
|
base.ProcessContext,
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,6 @@ func main() {
|
||||||
components := map[string]entrypoint{
|
components := map[string]entrypoint{
|
||||||
"appservice": personalities.Appservice,
|
"appservice": personalities.Appservice,
|
||||||
"clientapi": personalities.ClientAPI,
|
"clientapi": personalities.ClientAPI,
|
||||||
"eduserver": personalities.EDUServer,
|
|
||||||
"federationapi": personalities.FederationAPI,
|
"federationapi": personalities.FederationAPI,
|
||||||
"keyserver": personalities.KeyServer,
|
"keyserver": personalities.KeyServer,
|
||||||
"mediaapi": personalities.MediaAPI,
|
"mediaapi": personalities.MediaAPI,
|
||||||
|
|
|
||||||
|
|
@ -27,13 +27,12 @@ func ClientAPI(base *basepkg.BaseDendrite, cfg *config.Dendrite) {
|
||||||
asQuery := base.AppserviceHTTPClient()
|
asQuery := base.AppserviceHTTPClient()
|
||||||
rsAPI := base.RoomserverHTTPClient()
|
rsAPI := base.RoomserverHTTPClient()
|
||||||
fsAPI := base.FederationAPIHTTPClient()
|
fsAPI := base.FederationAPIHTTPClient()
|
||||||
eduInputAPI := base.EDUServerClient()
|
|
||||||
userAPI := base.UserAPIClient()
|
userAPI := base.UserAPIClient()
|
||||||
keyAPI := base.KeyServerHTTPClient()
|
keyAPI := base.KeyServerHTTPClient()
|
||||||
|
|
||||||
clientapi.AddPublicRoutes(
|
clientapi.AddPublicRoutes(
|
||||||
base.ProcessContext, base.PublicClientAPIMux, base.SynapseAdminMux, &base.Cfg.ClientAPI,
|
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,
|
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()
|
keyRing := fsAPI.KeyRing()
|
||||||
|
|
||||||
federationapi.AddPublicRoutes(
|
federationapi.AddPublicRoutes(
|
||||||
base.PublicFederationAPIMux, base.PublicKeyAPIMux, base.PublicWellKnownAPIMux,
|
base.ProcessContext, base.PublicFederationAPIMux, base.PublicKeyAPIMux, base.PublicWellKnownAPIMux,
|
||||||
&base.Cfg.FederationAPI, userAPI, federation, keyRing,
|
&base.Cfg.FederationAPI, userAPI, federation, keyRing,
|
||||||
rsAPI, fsAPI, base.EDUServerClient(), keyAPI,
|
rsAPI, fsAPI, keyAPI,
|
||||||
&base.Cfg.MSCs, nil,
|
&base.Cfg.MSCs, nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,18 +21,13 @@ import (
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/matrix-org/dendrite/appservice"
|
"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/conn"
|
||||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/rooms"
|
"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/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"
|
||||||
"github.com/matrix-org/dendrite/internal/httputil"
|
"github.com/matrix-org/dendrite/internal/httputil"
|
||||||
"github.com/matrix-org/dendrite/keyserver"
|
"github.com/matrix-org/dendrite/keyserver"
|
||||||
|
|
@ -48,6 +43,7 @@ import (
|
||||||
|
|
||||||
_ "github.com/matrix-org/go-sqlite3-js"
|
_ "github.com/matrix-org/go-sqlite3-js"
|
||||||
|
|
||||||
|
pineconeConnections "github.com/matrix-org/pinecone/connections"
|
||||||
pineconeRouter "github.com/matrix-org/pinecone/router"
|
pineconeRouter "github.com/matrix-org/pinecone/router"
|
||||||
pineconeSessions "github.com/matrix-org/pinecone/sessions"
|
pineconeSessions "github.com/matrix-org/pinecone/sessions"
|
||||||
)
|
)
|
||||||
|
|
@ -156,9 +152,10 @@ func startup() {
|
||||||
sk := generateKey()
|
sk := generateKey()
|
||||||
pk := sk.Public().(ed25519.PublicKey)
|
pk := sk.Public().(ed25519.PublicKey)
|
||||||
|
|
||||||
logger := log.New(os.Stdout, "", 0)
|
pRouter := pineconeRouter.NewRouter(logrus.WithField("pinecone", "router"), sk, false)
|
||||||
pRouter := pineconeRouter.NewRouter(logger, sk, false)
|
pSessions := pineconeSessions.NewSessions(logrus.WithField("pinecone", "sessions"), pRouter, []string{"matrix"})
|
||||||
pSessions := pineconeSessions.NewSessions(logger, pRouter)
|
pManager := pineconeConnections.NewConnectionManager(pRouter)
|
||||||
|
pManager.AddPeer("wss://pinecone.matrix.org/public")
|
||||||
|
|
||||||
cfg := &config.Dendrite{}
|
cfg := &config.Dendrite{}
|
||||||
cfg.Defaults(true)
|
cfg.Defaults(true)
|
||||||
|
|
@ -193,7 +190,6 @@ func startup() {
|
||||||
userAPI := userapi.NewInternalAPI(base, accountDB, &cfg.UserAPI, nil, keyAPI, rsAPI, base.PushGatewayHTTPClient())
|
userAPI := userapi.NewInternalAPI(base, accountDB, &cfg.UserAPI, nil, keyAPI, rsAPI, base.PushGatewayHTTPClient())
|
||||||
keyAPI.SetUserAPI(userAPI)
|
keyAPI.SetUserAPI(userAPI)
|
||||||
|
|
||||||
eduInputAPI := eduserver.NewInternalAPI(base, cache.New(), userAPI)
|
|
||||||
asQuery := appservice.NewInternalAPI(
|
asQuery := appservice.NewInternalAPI(
|
||||||
base, userAPI, rsAPI,
|
base, userAPI, rsAPI,
|
||||||
)
|
)
|
||||||
|
|
@ -208,12 +204,11 @@ func startup() {
|
||||||
FedClient: federation,
|
FedClient: federation,
|
||||||
KeyRing: keyRing,
|
KeyRing: keyRing,
|
||||||
|
|
||||||
AppserviceAPI: asQuery,
|
AppserviceAPI: asQuery,
|
||||||
EDUInternalAPI: eduInputAPI,
|
FederationAPI: fedSenderAPI,
|
||||||
FederationAPI: fedSenderAPI,
|
RoomserverAPI: rsAPI,
|
||||||
RoomserverAPI: rsAPI,
|
UserAPI: userAPI,
|
||||||
UserAPI: userAPI,
|
KeyAPI: keyAPI,
|
||||||
KeyAPI: keyAPI,
|
|
||||||
//ServerKeyAPI: serverKeyAPI,
|
//ServerKeyAPI: serverKeyAPI,
|
||||||
ExtPublicRoomsProvider: rooms.NewPineconeRoomProvider(pRouter, pSessions, fedSenderAPI, federation),
|
ExtPublicRoomsProvider: rooms.NewPineconeRoomProvider(pRouter, pSessions, fedSenderAPI, federation),
|
||||||
}
|
}
|
||||||
|
|
@ -232,7 +227,7 @@ func startup() {
|
||||||
httpRouter.PathPrefix(httputil.PublicClientPathPrefix).Handler(base.PublicClientAPIMux)
|
httpRouter.PathPrefix(httputil.PublicClientPathPrefix).Handler(base.PublicClientAPIMux)
|
||||||
httpRouter.PathPrefix(httputil.PublicMediaPathPrefix).Handler(base.PublicMediaAPIMux)
|
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.PublicFederationPathPrefix, base.PublicFederationAPIMux)
|
||||||
p2pRouter.Handle(httputil.PublicMediaPathPrefix, base.PublicMediaAPIMux)
|
p2pRouter.Handle(httputil.PublicMediaPathPrefix, base.PublicMediaAPIMux)
|
||||||
|
|
||||||
|
|
@ -244,20 +239,4 @@ func startup() {
|
||||||
}
|
}
|
||||||
s.ListenAndServe("fetch")
|
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/gorilla/mux"
|
||||||
"github.com/matrix-org/dendrite/appservice"
|
"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/federationapi"
|
||||||
"github.com/matrix-org/dendrite/internal/httputil"
|
"github.com/matrix-org/dendrite/internal/httputil"
|
||||||
"github.com/matrix-org/dendrite/keyserver"
|
"github.com/matrix-org/dendrite/keyserver"
|
||||||
|
|
@ -203,7 +201,6 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
rsAPI := roomserver.NewInternalAPI(base)
|
rsAPI := roomserver.NewInternalAPI(base)
|
||||||
eduInputAPI := eduserver.NewInternalAPI(base, cache.New(), userAPI)
|
|
||||||
asQuery := appservice.NewInternalAPI(
|
asQuery := appservice.NewInternalAPI(
|
||||||
base, userAPI, rsAPI,
|
base, userAPI, rsAPI,
|
||||||
)
|
)
|
||||||
|
|
@ -222,7 +219,6 @@ func main() {
|
||||||
KeyRing: &keyRing,
|
KeyRing: &keyRing,
|
||||||
|
|
||||||
AppserviceAPI: asQuery,
|
AppserviceAPI: asQuery,
|
||||||
EDUInternalAPI: eduInputAPI,
|
|
||||||
FederationSenderAPI: fedSenderAPI,
|
FederationSenderAPI: fedSenderAPI,
|
||||||
RoomserverAPI: rsAPI,
|
RoomserverAPI: rsAPI,
|
||||||
UserAPI: userAPI,
|
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.UserAPI.BCryptCost = bcrypt.MinCost
|
||||||
cfg.Global.JetStream.InMemory = true
|
cfg.Global.JetStream.InMemory = true
|
||||||
cfg.ClientAPI.RegistrationSharedSecret = "complement"
|
cfg.ClientAPI.RegistrationSharedSecret = "complement"
|
||||||
|
cfg.Global.Presence = config.PresenceOptions{
|
||||||
|
EnableInbound: true,
|
||||||
|
EnableOutbound: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
j, err := yaml.Marshal(cfg)
|
j, err := yaml.Marshal(cfg)
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,13 @@ global:
|
||||||
# to other servers and the federation API will not be exposed.
|
# to other servers and the federation API will not be exposed.
|
||||||
disable_federation: false
|
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 allows server admins to send messages to all users.
|
||||||
server_notices:
|
server_notices:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
@ -187,12 +194,6 @@ client_api:
|
||||||
threshold: 5
|
threshold: 5
|
||||||
cooloff_ms: 500
|
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.
|
# Configuration for the Federation API.
|
||||||
federation_api:
|
federation_api:
|
||||||
internal_api:
|
internal_api:
|
||||||
|
|
@ -206,12 +207,6 @@ federation_api:
|
||||||
max_idle_conns: 2
|
max_idle_conns: 2
|
||||||
conn_max_lifetime: -1
|
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
|
# 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.
|
# backoff is 2**x seconds, so 1 = 2 seconds, 2 = 4 seconds, 3 = 8 seconds etc.
|
||||||
send_max_retries: 16
|
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
|
We ask that everyone who contributes to the project signs off their
|
||||||
contributions, in accordance with the
|
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
|
* 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
|
## Building Dendrite
|
||||||
|
|
||||||
Start by cloning the code:
|
Start by cloning the code:
|
||||||
|
|
@ -45,9 +49,15 @@ cd dendrite
|
||||||
|
|
||||||
Then build it:
|
Then build it:
|
||||||
|
|
||||||
```bash
|
* Linux or UNIX-like systems:
|
||||||
./build.sh
|
```bash
|
||||||
```
|
./build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
* Windows:
|
||||||
|
```dos
|
||||||
|
build.cmd
|
||||||
|
```
|
||||||
|
|
||||||
## Install NATS Server
|
## Install NATS Server
|
||||||
|
|
||||||
|
|
@ -263,14 +273,6 @@ This manages end-to-end encryption keys for users.
|
||||||
./bin/dendrite-polylith-multi --config=dendrite.yaml keyserver
|
./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
|
#### User server
|
||||||
|
|
||||||
This manages user accounts, device access tokens and user account data,
|
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"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
eduserverAPI "github.com/matrix-org/dendrite/eduserver/api"
|
|
||||||
"github.com/matrix-org/dendrite/federationapi/queue"
|
"github.com/matrix-org/dendrite/federationapi/queue"
|
||||||
"github.com/matrix-org/dendrite/federationapi/storage"
|
"github.com/matrix-org/dendrite/federationapi/storage"
|
||||||
|
"github.com/matrix-org/dendrite/federationapi/types"
|
||||||
"github.com/matrix-org/dendrite/keyserver/api"
|
"github.com/matrix-org/dendrite/keyserver/api"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"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
|
// Pack the EDU and marshal it
|
||||||
edu := &gomatrixserverlib.EDU{
|
edu := &gomatrixserverlib.EDU{
|
||||||
Type: eduserverAPI.MSigningKeyUpdate,
|
Type: types.MSigningKeyUpdate,
|
||||||
Origin: string(t.serverName),
|
Origin: string(t.serverName),
|
||||||
}
|
}
|
||||||
if edu.Content, err = json.Marshal(output); err != nil {
|
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 (
|
import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
eduserverAPI "github.com/matrix-org/dendrite/eduserver/api"
|
|
||||||
"github.com/matrix-org/dendrite/federationapi/api"
|
"github.com/matrix-org/dendrite/federationapi/api"
|
||||||
federationAPI "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/consumers"
|
||||||
"github.com/matrix-org/dendrite/federationapi/internal"
|
"github.com/matrix-org/dendrite/federationapi/internal"
|
||||||
"github.com/matrix-org/dendrite/federationapi/inthttp"
|
"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/queue"
|
||||||
"github.com/matrix-org/dendrite/federationapi/statistics"
|
"github.com/matrix-org/dendrite/federationapi/statistics"
|
||||||
"github.com/matrix-org/dendrite/federationapi/storage"
|
"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/base"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
|
"github.com/matrix-org/dendrite/setup/process"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/sirupsen/logrus"
|
"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.
|
// AddPublicRoutes sets up and registers HTTP handlers on the base API muxes for the FederationAPI component.
|
||||||
func AddPublicRoutes(
|
func AddPublicRoutes(
|
||||||
|
process *process.ProcessContext,
|
||||||
fedRouter, keyRouter, wellKnownRouter *mux.Router,
|
fedRouter, keyRouter, wellKnownRouter *mux.Router,
|
||||||
cfg *config.FederationAPI,
|
cfg *config.FederationAPI,
|
||||||
userAPI userapi.UserInternalAPI,
|
userAPI userapi.UserInternalAPI,
|
||||||
|
|
@ -53,16 +55,27 @@ func AddPublicRoutes(
|
||||||
keyRing gomatrixserverlib.JSONVerifier,
|
keyRing gomatrixserverlib.JSONVerifier,
|
||||||
rsAPI roomserverAPI.RoomserverInternalAPI,
|
rsAPI roomserverAPI.RoomserverInternalAPI,
|
||||||
federationAPI federationAPI.FederationInternalAPI,
|
federationAPI federationAPI.FederationInternalAPI,
|
||||||
eduAPI eduserverAPI.EDUServerInputAPI,
|
|
||||||
keyAPI keyserverAPI.KeyInternalAPI,
|
keyAPI keyserverAPI.KeyInternalAPI,
|
||||||
mscCfg *config.MSCs,
|
mscCfg *config.MSCs,
|
||||||
servers federationAPI.ServersInRoomProvider,
|
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(
|
routing.Setup(
|
||||||
fedRouter, keyRouter, wellKnownRouter, cfg, rsAPI,
|
fedRouter, keyRouter, wellKnownRouter, cfg, rsAPI,
|
||||||
eduAPI, federationAPI, keyRing,
|
federationAPI, keyRing,
|
||||||
federation, userAPI, keyAPI, mscCfg,
|
federation, userAPI, keyAPI, mscCfg,
|
||||||
servers,
|
servers, producer,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,19 +125,36 @@ func NewInternalAPI(
|
||||||
if err = rsConsumer.Start(); err != nil {
|
if err = rsConsumer.Start(); err != nil {
|
||||||
logrus.WithError(err).Panic("failed to start room server consumer")
|
logrus.WithError(err).Panic("failed to start room server consumer")
|
||||||
}
|
}
|
||||||
|
tsConsumer := consumers.NewOutputSendToDeviceConsumer(
|
||||||
tsConsumer := consumers.NewOutputEDUConsumer(
|
|
||||||
base.ProcessContext, cfg, js, queues, federationDB,
|
base.ProcessContext, cfg, js, queues, federationDB,
|
||||||
)
|
)
|
||||||
if err := tsConsumer.Start(); err != nil {
|
if err = tsConsumer.Start(); err != nil {
|
||||||
logrus.WithError(err).Panic("failed to start typing server consumer")
|
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(
|
keyConsumer := consumers.NewKeyChangeConsumer(
|
||||||
base.ProcessContext, &base.Cfg.KeyServer, js, queues, federationDB, rsAPI,
|
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")
|
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)
|
return internal.NewFederationInternalAPI(federationDB, cfg, rsAPI, federation, stats, caches, queues, keyRing)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ func TestRoomsV3URLEscapeDoNot404(t *testing.T) {
|
||||||
fsAPI := base.FederationAPIHTTPClient()
|
fsAPI := base.FederationAPIHTTPClient()
|
||||||
// TODO: This is pretty fragile, as if anything calls anything on these nils this test will break.
|
// 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.
|
// 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)
|
baseURL, cancel := test.ListenAndServe(t, base.PublicFederationAPIMux, true)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
serverName := gomatrixserverlib.ServerName(strings.TrimPrefix(baseURL, "https://"))
|
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
|
// we have the peek state now so let's process regardless of whether upstream gives up
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
|
|
||||||
respState := respPeek.ToRespState()
|
respState := respPeek.ToRespState()
|
||||||
authEvents := respState.AuthEvents.UntrustedEvents(respPeek.RoomVersion)
|
|
||||||
// authenticate the state returned (check its auth events etc)
|
// authenticate the state returned (check its auth events etc)
|
||||||
// the equivalent of CheckSendJoinResponse()
|
// 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 {
|
if err = sanityCheckAuthChain(authEvents); err != nil {
|
||||||
return fmt.Errorf("sanityCheckAuthChain: %w", err)
|
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 we've got this far, the remote server is peeking.
|
||||||
if renewing {
|
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.
|
// Look up which servers we have pending items for and then rehydrate those queues.
|
||||||
if !disabled {
|
if !disabled {
|
||||||
time.AfterFunc(time.Second*5, func() {
|
serverNames := map[gomatrixserverlib.ServerName]struct{}{}
|
||||||
serverNames := map[gomatrixserverlib.ServerName]struct{}{}
|
if names, err := db.GetPendingPDUServerNames(context.Background()); err == nil {
|
||||||
if names, err := db.GetPendingPDUServerNames(context.Background()); err == nil {
|
for _, serverName := range names {
|
||||||
for _, serverName := range names {
|
serverNames[serverName] = struct{}{}
|
||||||
serverNames[serverName] = struct{}{}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.WithError(err).Error("Failed to get PDU server names for destination queue hydration")
|
|
||||||
}
|
}
|
||||||
if names, err := db.GetPendingEDUServerNames(context.Background()); err == nil {
|
} else {
|
||||||
for _, serverName := range names {
|
log.WithError(err).Error("Failed to get PDU server names for destination queue hydration")
|
||||||
serverNames[serverName] = struct{}{}
|
}
|
||||||
}
|
if names, err := db.GetPendingEDUServerNames(context.Background()); err == nil {
|
||||||
} else {
|
for _, serverName := range names {
|
||||||
log.WithError(err).Error("Failed to get EDU server names for destination queue hydration")
|
serverNames[serverName] = struct{}{}
|
||||||
}
|
}
|
||||||
for serverName := range serverNames {
|
} else {
|
||||||
if queue := queues.getQueue(serverName); queue != nil {
|
log.WithError(err).Error("Failed to get EDU server names for destination queue hydration")
|
||||||
queue.wakeQueueIfNeeded()
|
}
|
||||||
}
|
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
|
return queues
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ import (
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
eduserverAPI "github.com/matrix-org/dendrite/eduserver/api"
|
|
||||||
federationAPI "github.com/matrix-org/dendrite/federationapi/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"
|
||||||
"github.com/matrix-org/dendrite/internal/httputil"
|
"github.com/matrix-org/dendrite/internal/httputil"
|
||||||
keyserverAPI "github.com/matrix-org/dendrite/keyserver/api"
|
keyserverAPI "github.com/matrix-org/dendrite/keyserver/api"
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -44,7 +45,6 @@ func Setup(
|
||||||
fedMux, keyMux, wkMux *mux.Router,
|
fedMux, keyMux, wkMux *mux.Router,
|
||||||
cfg *config.FederationAPI,
|
cfg *config.FederationAPI,
|
||||||
rsAPI roomserverAPI.RoomserverInternalAPI,
|
rsAPI roomserverAPI.RoomserverInternalAPI,
|
||||||
eduAPI eduserverAPI.EDUServerInputAPI,
|
|
||||||
fsAPI federationAPI.FederationInternalAPI,
|
fsAPI federationAPI.FederationInternalAPI,
|
||||||
keys gomatrixserverlib.JSONVerifier,
|
keys gomatrixserverlib.JSONVerifier,
|
||||||
federation *gomatrixserverlib.FederationClient,
|
federation *gomatrixserverlib.FederationClient,
|
||||||
|
|
@ -52,7 +52,12 @@ func Setup(
|
||||||
keyAPI keyserverAPI.KeyInternalAPI,
|
keyAPI keyserverAPI.KeyInternalAPI,
|
||||||
mscCfg *config.MSCs,
|
mscCfg *config.MSCs,
|
||||||
servers federationAPI.ServersInRoomProvider,
|
servers federationAPI.ServersInRoomProvider,
|
||||||
|
producer *producers.SyncAPIProducer,
|
||||||
) {
|
) {
|
||||||
|
prometheus.MustRegister(
|
||||||
|
pduCountTotal, eduCountTotal,
|
||||||
|
)
|
||||||
|
|
||||||
v2keysmux := keyMux.PathPrefix("/v2").Subrouter()
|
v2keysmux := keyMux.PathPrefix("/v2").Subrouter()
|
||||||
v1fedmux := fedMux.PathPrefix("/v1").Subrouter()
|
v1fedmux := fedMux.PathPrefix("/v1").Subrouter()
|
||||||
v2fedmux := fedMux.PathPrefix("/v2").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 {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
return Send(
|
return Send(
|
||||||
httpReq, request, gomatrixserverlib.TransactionID(vars["txnID"]),
|
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)
|
)).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,14 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
eduserverAPI "github.com/matrix-org/dendrite/eduserver/api"
|
|
||||||
federationAPI "github.com/matrix-org/dendrite/federationapi/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"
|
"github.com/matrix-org/dendrite/internal"
|
||||||
keyapi "github.com/matrix-org/dendrite/keyserver/api"
|
keyapi "github.com/matrix-org/dendrite/keyserver/api"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
|
syncTypes "github.com/matrix-org/dendrite/syncapi/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"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
|
var inFlightTxnsPerOrigin sync.Map // transaction ID -> chan util.JSONResponse
|
||||||
|
|
||||||
// Send implements /_matrix/federation/v1/send/{txnID}
|
// Send implements /_matrix/federation/v1/send/{txnID}
|
||||||
|
|
@ -87,12 +83,12 @@ func Send(
|
||||||
txnID gomatrixserverlib.TransactionID,
|
txnID gomatrixserverlib.TransactionID,
|
||||||
cfg *config.FederationAPI,
|
cfg *config.FederationAPI,
|
||||||
rsAPI api.RoomserverInternalAPI,
|
rsAPI api.RoomserverInternalAPI,
|
||||||
eduAPI eduserverAPI.EDUServerInputAPI,
|
|
||||||
keyAPI keyapi.KeyInternalAPI,
|
keyAPI keyapi.KeyInternalAPI,
|
||||||
keys gomatrixserverlib.JSONVerifier,
|
keys gomatrixserverlib.JSONVerifier,
|
||||||
federation *gomatrixserverlib.FederationClient,
|
federation *gomatrixserverlib.FederationClient,
|
||||||
mu *internal.MutexByRoom,
|
mu *internal.MutexByRoom,
|
||||||
servers federationAPI.ServersInRoomProvider,
|
servers federationAPI.ServersInRoomProvider,
|
||||||
|
producer *producers.SyncAPIProducer,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
// First we should check if this origin has already submitted this
|
// 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,
|
// txn ID to us. If they have and the txnIDs map contains an entry,
|
||||||
|
|
@ -126,13 +122,14 @@ func Send(
|
||||||
defer inFlightTxnsPerOrigin.Delete(index)
|
defer inFlightTxnsPerOrigin.Delete(index)
|
||||||
|
|
||||||
t := txnReq{
|
t := txnReq{
|
||||||
rsAPI: rsAPI,
|
rsAPI: rsAPI,
|
||||||
eduAPI: eduAPI,
|
keys: keys,
|
||||||
keys: keys,
|
federation: federation,
|
||||||
federation: federation,
|
servers: servers,
|
||||||
servers: servers,
|
keyAPI: keyAPI,
|
||||||
keyAPI: keyAPI,
|
roomsMu: mu,
|
||||||
roomsMu: mu,
|
producer: producer,
|
||||||
|
inboundPresenceEnabled: cfg.Matrix.Presence.EnableInbound,
|
||||||
}
|
}
|
||||||
|
|
||||||
var txnEvents struct {
|
var txnEvents struct {
|
||||||
|
|
@ -184,13 +181,14 @@ func Send(
|
||||||
|
|
||||||
type txnReq struct {
|
type txnReq struct {
|
||||||
gomatrixserverlib.Transaction
|
gomatrixserverlib.Transaction
|
||||||
rsAPI api.RoomserverInternalAPI
|
rsAPI api.RoomserverInternalAPI
|
||||||
eduAPI eduserverAPI.EDUServerInputAPI
|
keyAPI keyapi.KeyInternalAPI
|
||||||
keyAPI keyapi.KeyInternalAPI
|
keys gomatrixserverlib.JSONVerifier
|
||||||
keys gomatrixserverlib.JSONVerifier
|
federation txnFederationClient
|
||||||
federation txnFederationClient
|
roomsMu *internal.MutexByRoom
|
||||||
roomsMu *internal.MutexByRoom
|
servers federationAPI.ServersInRoomProvider
|
||||||
servers federationAPI.ServersInRoomProvider
|
producer *producers.SyncAPIProducer
|
||||||
|
inboundPresenceEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// A subset of FederationClient functionality that txn requires. Useful for testing.
|
// 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)
|
util.GetLogger(ctx).Debugf("Dropping typing event where sender domain (%q) doesn't match origin (%q)", domain, t.Origin)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := eduserverAPI.SendTyping(ctx, t.eduAPI, typingPayload.UserID, typingPayload.RoomID, typingPayload.Typing, 30*1000); err != nil {
|
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 edu server")
|
util.GetLogger(ctx).WithError(err).Error("Failed to send typing event to JetStream")
|
||||||
}
|
}
|
||||||
case gomatrixserverlib.MDirectToDevice:
|
case gomatrixserverlib.MDirectToDevice:
|
||||||
// https://matrix.org/docs/spec/server_server/r0.1.3#m-direct-to-device-schema
|
// 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 userID, byUser := range directPayload.Messages {
|
||||||
for deviceID, message := range byUser {
|
for deviceID, message := range byUser {
|
||||||
// TODO: check that the user and the device actually exist here
|
// 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{
|
util.GetLogger(ctx).WithError(err).WithFields(logrus.Fields{
|
||||||
"sender": directPayload.Sender,
|
"sender": directPayload.Sender,
|
||||||
"user_id": userID,
|
"user_id": userID,
|
||||||
"device_id": deviceID,
|
"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)
|
t.processDeviceListUpdate(ctx, e)
|
||||||
case gomatrixserverlib.MReceipt:
|
case gomatrixserverlib.MReceipt:
|
||||||
// https://matrix.org/docs/spec/server_server/r0.1.4#receipts
|
// 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 {
|
if err := json.Unmarshal(e.Content, &payload); err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Debug("Failed to unmarshal receipt event")
|
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,
|
"user_id": userID,
|
||||||
"room_id": roomID,
|
"room_id": roomID,
|
||||||
"events": mread.EventIDs,
|
"events": mread.EventIDs,
|
||||||
}).Error("Failed to send receipt event to edu server")
|
}).Error("Failed to send receipt event to JetStream")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case eduserverAPI.MSigningKeyUpdate:
|
case types.MSigningKeyUpdate:
|
||||||
if err := t.processSigningKeyUpdate(ctx, e); err != nil {
|
if err := t.processSigningKeyUpdate(ctx, e); err != nil {
|
||||||
logrus.WithError(err).Errorf("Failed to process signing key update")
|
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:
|
default:
|
||||||
util.GetLogger(ctx).WithField("type", e.Type).Debug("Unhandled EDU")
|
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 {
|
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 {
|
if err := json.Unmarshal(e.Content, &updatePayload); err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).WithFields(logrus.Fields{
|
util.GetLogger(ctx).WithError(err).WithFields(logrus.Fields{
|
||||||
"user_id": updatePayload.UserID,
|
"user_id": updatePayload.UserID,
|
||||||
|
|
@ -422,7 +444,7 @@ func (t *txnReq) processSigningKeyUpdate(ctx context.Context, e gomatrixserverli
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// processReceiptEvent sends receipt events to the edu server
|
// processReceiptEvent sends receipt events to JetStream
|
||||||
func (t *txnReq) processReceiptEvent(ctx context.Context,
|
func (t *txnReq) processReceiptEvent(ctx context.Context,
|
||||||
userID, roomID, receiptType string,
|
userID, roomID, receiptType string,
|
||||||
timestamp gomatrixserverlib.Timestamp,
|
timestamp gomatrixserverlib.Timestamp,
|
||||||
|
|
@ -430,17 +452,7 @@ func (t *txnReq) processReceiptEvent(ctx context.Context,
|
||||||
) error {
|
) error {
|
||||||
// store every event
|
// store every event
|
||||||
for _, eventID := range eventIDs {
|
for _, eventID := range eventIDs {
|
||||||
req := eduserverAPI.InputReceiptEventRequest{
|
if err := t.producer.SendReceipt(ctx, userID, roomID, eventID, receiptType, timestamp); err != nil {
|
||||||
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 {
|
|
||||||
return fmt.Errorf("unable to set receipt event: %w", err)
|
return fmt.Errorf("unable to set receipt event: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
eduAPI "github.com/matrix-org/dendrite/eduserver/api"
|
|
||||||
"github.com/matrix-org/dendrite/internal"
|
"github.com/matrix-org/dendrite/internal"
|
||||||
"github.com/matrix-org/dendrite/internal/test"
|
"github.com/matrix-org/dendrite/internal/test"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"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 {
|
type testRoomserverAPI struct {
|
||||||
api.RoomserverInternalAPITrace
|
api.RoomserverInternalAPITrace
|
||||||
inputRoomEvents []api.InputRoomEvent
|
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 {
|
func mustCreateTransaction(rsAPI api.RoomserverInternalAPI, fedClient txnFederationClient, pdus []json.RawMessage) *txnReq {
|
||||||
t := &txnReq{
|
t := &txnReq{
|
||||||
rsAPI: rsAPI,
|
rsAPI: rsAPI,
|
||||||
eduAPI: &testEDUProducer{},
|
|
||||||
keys: &test.NopJSONVerifier{},
|
keys: &test.NopJSONVerifier{},
|
||||||
federation: fedClient,
|
federation: fedClient,
|
||||||
roomsMu: internal.NewMutexByRoom(),
|
roomsMu: internal.NewMutexByRoom(),
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ import (
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"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.
|
// A JoinedHost is a server that is joined to a matrix room.
|
||||||
type JoinedHost struct {
|
type JoinedHost struct {
|
||||||
// The MemberEventID of a m.room.member join event.
|
// The MemberEventID of a m.room.member join event.
|
||||||
|
|
@ -51,3 +53,28 @@ type InboundPeek struct {
|
||||||
RenewedTimestamp int64
|
RenewedTimestamp int64
|
||||||
RenewalInterval 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/MFAshby/stdemuxerhook v1.0.0
|
||||||
github.com/Masterminds/semver/v3 v3.1.1
|
github.com/Masterminds/semver/v3 v3.1.1
|
||||||
github.com/codeclysm/extract v2.2.0+incompatible
|
github.com/codeclysm/extract v2.2.0+incompatible
|
||||||
github.com/containerd/containerd v1.5.9 // indirect
|
github.com/containerd/containerd v1.6.2 // indirect
|
||||||
github.com/docker/docker v20.10.12+incompatible
|
github.com/docker/docker v20.10.14+incompatible
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/frankban/quicktest v1.14.0 // indirect
|
github.com/frankban/quicktest v1.14.3 // indirect
|
||||||
github.com/getsentry/sentry-go v0.12.0
|
github.com/getsentry/sentry-go v0.13.0
|
||||||
github.com/gologme/log v1.3.0
|
github.com/gologme/log v1.3.0
|
||||||
github.com/google/go-cmp v0.5.6
|
github.com/google/go-cmp v0.5.7
|
||||||
github.com/google/uuid v1.2.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/gorilla/mux v1.8.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/h2non/filetype v1.1.3 // indirect
|
||||||
github.com/hashicorp/golang-lru v0.5.4
|
github.com/hashicorp/golang-lru v0.5.4
|
||||||
github.com/juju/testing v0.0.0-20211215003918-77eb13d6cad2 // indirect
|
github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494 // indirect
|
||||||
github.com/lib/pq v1.10.4
|
github.com/lib/pq v1.10.5
|
||||||
github.com/libp2p/go-libp2p v0.13.0
|
github.com/libp2p/go-libp2p v0.13.0
|
||||||
github.com/libp2p/go-libp2p-circuit v0.4.0
|
github.com/libp2p/go-libp2p-circuit v0.4.0
|
||||||
github.com/libp2p/go-libp2p-core v0.8.3
|
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-http-js-libp2p v0.0.0-20200518170932-783164aeeda4
|
||||||
github.com/matrix-org/go-sqlite3-js v0.0.0-20210709140738-b0d1ba599a6d
|
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/gomatrix v0.0.0-20210324163249-be2af5ef2e16
|
||||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20220317164600-0980b7f341e0
|
github.com/matrix-org/gomatrixserverlib v0.0.0-20220405134050-301e340659d5
|
||||||
github.com/matrix-org/pinecone v0.0.0-20220323142759-6fb077377278
|
github.com/matrix-org/pinecone v0.0.0-20220408153826-2999ea29ed48
|
||||||
github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4
|
github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4
|
||||||
github.com/mattn/go-sqlite3 v1.14.10
|
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-server/v2 v2.7.4-0.20220309205833-773636c1c5bb
|
||||||
github.com/nats-io/nats.go v1.13.1-0.20220308171302-2f2f6968e98d
|
github.com/nats-io/nats.go v1.13.1-0.20220308171302-2f2f6968e98d
|
||||||
github.com/neilalexander/utp v0.1.1-0.20210727203401-54ae7b1cd5f9
|
github.com/neilalexander/utp v0.1.1-0.20210727203401-54ae7b1cd5f9
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||||
github.com/ngrok/sqlmw v0.0.0-20211220175533-9d16fdc47b31
|
github.com/ngrok/sqlmw v0.0.0-20211220175533-9d16fdc47b31
|
||||||
github.com/onsi/ginkgo v1.16.4 // indirect
|
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||||
github.com/onsi/gomega v1.13.0 // indirect
|
|
||||||
github.com/opentracing/opentracing-go v1.2.0
|
github.com/opentracing/opentracing-go v1.2.0
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
|
|
@ -62,15 +60,13 @@ require (
|
||||||
github.com/uber/jaeger-lib v2.4.1+incompatible
|
github.com/uber/jaeger-lib v2.4.1+incompatible
|
||||||
github.com/yggdrasil-network/yggdrasil-go v0.4.3
|
github.com/yggdrasil-network/yggdrasil-go v0.4.3
|
||||||
go.uber.org/atomic v1.9.0
|
go.uber.org/atomic v1.9.0
|
||||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292
|
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
|
||||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
|
golang.org/x/image v0.0.0-20220321031419-a8550c1d254a
|
||||||
golang.org/x/mobile v0.0.0-20220112015953-858099ff7816
|
golang.org/x/mobile v0.0.0-20220407111146-e579adbbc4a2
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
|
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3
|
||||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
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.v2 v2.4.0
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
|
||||||
nhooyr.io/websocket v1.8.7
|
nhooyr.io/websocket v1.8.7
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package cache
|
package caching
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -53,8 +53,8 @@ func (t *EDUCache) newRoomData() *roomData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new EDUCache initialised for use.
|
// NewTypingCache returns a new EDUCache initialised for use.
|
||||||
func New() *EDUCache {
|
func NewTypingCache() *EDUCache {
|
||||||
return &EDUCache{data: make(map[string]*roomData)}
|
return &EDUCache{data: make(map[string]*roomData)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package cache
|
package caching
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -24,9 +24,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEDUCache(t *testing.T) {
|
func TestEDUCache(t *testing.T) {
|
||||||
tCache := New()
|
tCache := NewTypingCache()
|
||||||
if tCache == nil {
|
if tCache == nil {
|
||||||
t.Fatal("New failed")
|
t.Fatal("NewTypingCache failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("AddTypingUser", func(t *testing.T) {
|
t.Run("AddTypingUser", func(t *testing.T) {
|
||||||
|
|
@ -17,6 +17,8 @@ package eventutil
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/syncapi/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrProfileNoExists is returned when trying to lookup a user's profile that
|
// 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
|
// AccountData represents account data sent from the client API server to the
|
||||||
// sync API server
|
// sync API server
|
||||||
type AccountData struct {
|
type AccountData struct {
|
||||||
RoomID string `json:"room_id"`
|
RoomID string `json:"room_id"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
ReadMarker *ReadMarkerJSON `json:"read_marker,omitempty"` // optional
|
ReadMarker *ReadMarkerJSON `json:"read_marker,omitempty"` // optional
|
||||||
|
IgnoredUsers *types.IgnoredUsers `json:"ignored_users,omitempty"` // optional
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReadMarkerJSON struct {
|
type ReadMarkerJSON struct {
|
||||||
|
|
|
||||||
|
|
@ -169,8 +169,9 @@ func MakeHTMLAPI(metricsName string, f func(http.ResponseWriter, *http.Request)
|
||||||
return promhttp.InstrumentHandlerCounter(
|
return promhttp.InstrumentHandlerCounter(
|
||||||
promauto.NewCounterVec(
|
promauto.NewCounterVec(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: metricsName,
|
Name: metricsName,
|
||||||
Help: "Total number of http requests for HTML resources",
|
Help: "Total number of http requests for HTML resources",
|
||||||
|
Namespace: "dendrite",
|
||||||
},
|
},
|
||||||
[]string{"code"},
|
[]string{"code"},
|
||||||
),
|
),
|
||||||
|
|
@ -201,7 +202,28 @@ func MakeInternalAPI(metricsName string, f func(*http.Request) util.JSONResponse
|
||||||
h.ServeHTTP(w, req)
|
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.
|
// 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.ServerName = gomatrixserverlib.ServerName(assignAddress())
|
||||||
cfg.Global.PrivateKeyPath = config.Path(serverKeyPath)
|
cfg.Global.PrivateKeyPath = config.Path(serverKeyPath)
|
||||||
|
|
||||||
cfg.FederationAPI.FederationCertificatePaths = []config.Path{config.Path(tlsCertPath)}
|
|
||||||
|
|
||||||
cfg.MediaAPI.BasePath = config.Path(mediaBasePath)
|
cfg.MediaAPI.BasePath = config.Path(mediaBasePath)
|
||||||
|
|
||||||
cfg.Global.JetStream.Addresses = []string{kafkaURI}
|
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.UserAPI.AccountDatabase.ConnectionString = config.DataSource(database)
|
||||||
|
|
||||||
cfg.AppServiceAPI.InternalAPI.Listen = assignAddress()
|
cfg.AppServiceAPI.InternalAPI.Listen = assignAddress()
|
||||||
cfg.EDUServer.InternalAPI.Listen = assignAddress()
|
|
||||||
cfg.FederationAPI.InternalAPI.Listen = assignAddress()
|
cfg.FederationAPI.InternalAPI.Listen = assignAddress()
|
||||||
cfg.KeyServer.InternalAPI.Listen = assignAddress()
|
cfg.KeyServer.InternalAPI.Listen = assignAddress()
|
||||||
cfg.MediaAPI.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.UserAPI.InternalAPI.Listen = assignAddress()
|
||||||
|
|
||||||
cfg.AppServiceAPI.InternalAPI.Connect = cfg.AppServiceAPI.InternalAPI.Listen
|
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.FederationAPI.InternalAPI.Connect = cfg.FederationAPI.InternalAPI.Listen
|
||||||
cfg.KeyServer.InternalAPI.Connect = cfg.KeyServer.InternalAPI.Listen
|
cfg.KeyServer.InternalAPI.Connect = cfg.KeyServer.InternalAPI.Listen
|
||||||
cfg.MediaAPI.InternalAPI.Connect = cfg.MediaAPI.InternalAPI.Listen
|
cfg.MediaAPI.InternalAPI.Connect = cfg.MediaAPI.InternalAPI.Listen
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ var build string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
VersionMajor = 0
|
VersionMajor = 0
|
||||||
VersionMinor = 7
|
VersionMinor = 8
|
||||||
VersionPatch = 0
|
VersionPatch = 1
|
||||||
VersionTag = "" // example: "rc1"
|
VersionTag = "" // example: "rc1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
eduapi "github.com/matrix-org/dendrite/eduserver/api"
|
|
||||||
"github.com/matrix-org/dendrite/keyserver/types"
|
"github.com/matrix-org/dendrite/keyserver/types"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
@ -66,14 +65,25 @@ const (
|
||||||
|
|
||||||
// DeviceMessage represents the message produced into Kafka by the key server.
|
// DeviceMessage represents the message produced into Kafka by the key server.
|
||||||
type DeviceMessage struct {
|
type DeviceMessage struct {
|
||||||
Type DeviceMessageType `json:"Type,omitempty"`
|
Type DeviceMessageType `json:"Type,omitempty"`
|
||||||
*DeviceKeys `json:"DeviceKeys,omitempty"`
|
*DeviceKeys `json:"DeviceKeys,omitempty"`
|
||||||
*eduapi.OutputCrossSigningKeyUpdate `json:"CrossSigningKeyUpdate,omitempty"`
|
*OutputCrossSigningKeyUpdate `json:"CrossSigningKeyUpdate,omitempty"`
|
||||||
// A monotonically increasing number which represents device changes for this user.
|
// A monotonically increasing number which represents device changes for this user.
|
||||||
StreamID int64
|
StreamID int64
|
||||||
DeviceChangeID 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
|
// DeviceKeysEqual returns true if the device keys updates contain the
|
||||||
// same display name and key JSON. This will return false if either of
|
// 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
|
// the updates is not a device keys update, or if the user ID/device ID
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
eduserverAPI "github.com/matrix-org/dendrite/eduserver/api"
|
|
||||||
"github.com/matrix-org/dendrite/keyserver/api"
|
"github.com/matrix-org/dendrite/keyserver/api"
|
||||||
"github.com/matrix-org/dendrite/keyserver/types"
|
"github.com/matrix-org/dendrite/keyserver/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"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.
|
// Finally, generate a notification that we updated the keys.
|
||||||
update := eduserverAPI.CrossSigningKeyUpdate{
|
update := api.CrossSigningKeyUpdate{
|
||||||
UserID: req.UserID,
|
UserID: req.UserID,
|
||||||
}
|
}
|
||||||
if mk, ok := byPurpose[gomatrixserverlib.CrossSigningKeyPurposeMaster]; ok {
|
if mk, ok := byPurpose[gomatrixserverlib.CrossSigningKeyPurposeMaster]; ok {
|
||||||
|
|
@ -337,7 +336,7 @@ func (a *KeyInternalAPI) PerformUploadDeviceSignatures(ctx context.Context, req
|
||||||
for userID := range req.Signatures {
|
for userID := range req.Signatures {
|
||||||
masterKey := queryRes.MasterKeys[userID]
|
masterKey := queryRes.MasterKeys[userID]
|
||||||
selfSigningKey := queryRes.SelfSigningKeys[userID]
|
selfSigningKey := queryRes.SelfSigningKeys[userID]
|
||||||
update := eduserverAPI.CrossSigningKeyUpdate{
|
update := api.CrossSigningKeyUpdate{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
MasterKey: &masterKey,
|
MasterKey: &masterKey,
|
||||||
SelfSigningKey: &selfSigningKey,
|
SelfSigningKey: &selfSigningKey,
|
||||||
|
|
|
||||||
|
|
@ -157,8 +157,15 @@ func (u *DeviceListUpdater) Start() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
for _, userID := range staleLists {
|
||||||
u.notifyWorkers(userID)
|
time.AfterFunc(offset, func() {
|
||||||
|
u.notifyWorkers(userID)
|
||||||
|
})
|
||||||
|
offset += step
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
eduapi "github.com/matrix-org/dendrite/eduserver/api"
|
|
||||||
"github.com/matrix-org/dendrite/keyserver/api"
|
"github.com/matrix-org/dendrite/keyserver/api"
|
||||||
"github.com/matrix-org/dendrite/keyserver/storage"
|
"github.com/matrix-org/dendrite/keyserver/storage"
|
||||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
|
|
@ -70,10 +69,10 @@ func (p *KeyChange) ProduceKeyChanges(keys []api.DeviceMessage) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *KeyChange) ProduceSigningKeyUpdate(key eduapi.CrossSigningKeyUpdate) error {
|
func (p *KeyChange) ProduceSigningKeyUpdate(key api.CrossSigningKeyUpdate) error {
|
||||||
output := &api.DeviceMessage{
|
output := &api.DeviceMessage{
|
||||||
Type: api.TypeCrossSigningUpdate,
|
Type: api.TypeCrossSigningUpdate,
|
||||||
OutputCrossSigningKeyUpdate: &eduapi.OutputCrossSigningKeyUpdate{
|
OutputCrossSigningKeyUpdate: &api.OutputCrossSigningKeyUpdate{
|
||||||
CrossSigningKeyUpdate: key,
|
CrossSigningKeyUpdate: key,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ func (r *uploadRequest) doUpload(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if temp file size exceeds max file size configuration
|
// 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
|
fileutils.RemoveDir(tmpDir, r.Logger) // delete temp file
|
||||||
return requestEntityTooLargeJSONResponse(*cfg.MaxFileSizeBytes)
|
return requestEntityTooLargeJSONResponse(*cfg.MaxFileSizeBytes)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ func Test_uploadRequest_doUpload(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
maxSize := config.FileSizeBytes(8)
|
maxSize := config.FileSizeBytes(8)
|
||||||
|
unlimitedSize := config.FileSizeBytes(0)
|
||||||
logger := log.New().WithField("mediaapi", "test")
|
logger := log.New().WithField("mediaapi", "test")
|
||||||
testdataPath := filepath.Join(wd, "./testdata")
|
testdataPath := filepath.Join(wd, "./testdata")
|
||||||
|
|
||||||
|
|
@ -117,6 +118,27 @@ func Test_uploadRequest_doUpload(t *testing.T) {
|
||||||
},
|
},
|
||||||
want: requestEntityTooLargeJSONResponse(maxSize),
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
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 forgets a rooms history for a specific user
|
||||||
PerformForget(ctx context.Context, req *PerformForgetRequest, resp *PerformForgetResponse) error
|
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.
|
// Asks for the default room version as preferred by the server.
|
||||||
QueryRoomVersionCapabilities(
|
QueryRoomVersionCapabilities(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,15 @@ func (t *RoomserverInternalAPITrace) PerformUnpeek(
|
||||||
util.GetLogger(ctx).Infof("PerformUnpeek req=%+v res=%+v", js(req), js(res))
|
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(
|
func (t *RoomserverInternalAPITrace) PerformJoin(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *PerformJoinRequest,
|
req *PerformJoinRequest,
|
||||||
|
|
|
||||||
|
|
@ -203,3 +203,14 @@ type PerformForgetRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type PerformForgetResponse 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 {
|
type QueryMembershipsForRoomRequest struct {
|
||||||
// If true, only returns the membership events of "join" membership
|
// If true, only returns the membership events of "join" membership
|
||||||
JoinedOnly bool `json:"joined_only"`
|
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
|
// ID of the room to fetch memberships from
|
||||||
RoomID string `json:"room_id"`
|
RoomID string `json:"room_id"`
|
||||||
// Optional - ID of the user sending the request, for checking if the
|
// Optional - ID of the user sending the request, for checking if the
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ type RoomserverInternalAPI struct {
|
||||||
*perform.Publisher
|
*perform.Publisher
|
||||||
*perform.Backfiller
|
*perform.Backfiller
|
||||||
*perform.Forgetter
|
*perform.Forgetter
|
||||||
|
*perform.Upgrader
|
||||||
ProcessContext *process.ProcessContext
|
ProcessContext *process.ProcessContext
|
||||||
DB storage.Database
|
DB storage.Database
|
||||||
Cfg *config.RoomServer
|
Cfg *config.RoomServer
|
||||||
|
|
@ -159,6 +160,10 @@ func (r *RoomserverInternalAPI) SetFederationAPI(fsAPI fsAPI.FederationInternalA
|
||||||
r.Forgetter = &perform.Forgetter{
|
r.Forgetter = &perform.Forgetter{
|
||||||
DB: r.DB,
|
DB: r.DB,
|
||||||
}
|
}
|
||||||
|
r.Upgrader = &perform.Upgrader{
|
||||||
|
Cfg: r.Cfg,
|
||||||
|
URSAPI: r,
|
||||||
|
}
|
||||||
|
|
||||||
if err := r.Inputer.Start(); err != nil {
|
if err := r.Inputer.Start(); err != nil {
|
||||||
logrus.WithError(err).Panic("failed to start roomserver input API")
|
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
|
// 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.
|
// own consumer. If we don't, we'll start one.
|
||||||
func (r *Inputer) Start() error {
|
func (r *Inputer) Start() error {
|
||||||
|
prometheus.MustRegister(roomserverInputBackpressure, processRoomEventDuration)
|
||||||
_, err := r.JetStream.Subscribe(
|
_, err := r.JetStream.Subscribe(
|
||||||
"", // This is blank because we specified it in BindStream.
|
"", // This is blank because we specified it in BindStream.
|
||||||
func(m *nats.Msg) {
|
func(m *nats.Msg) {
|
||||||
|
|
@ -421,10 +422,6 @@ func (r *Inputer) WriteOutputEvents(roomID string, updates []api.OutputEvent) er
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
prometheus.MustRegister(roomserverInputBackpressure)
|
|
||||||
}
|
|
||||||
|
|
||||||
var roomserverInputBackpressure = prometheus.NewGaugeVec(
|
var roomserverInputBackpressure = prometheus.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Namespace: "dendrite",
|
Namespace: "dendrite",
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,6 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
prometheus.MustRegister(processRoomEventDuration)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Does this value make sense?
|
// TODO: Does this value make sense?
|
||||||
const MaximumMissingProcessingTime = time.Minute * 2
|
const MaximumMissingProcessingTime = time.Minute * 2
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -613,12 +613,13 @@ func (t *missingStateReq) lookupMissingStateViaState(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Check that the returned state is valid.
|
// 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
|
return nil, err
|
||||||
}
|
}
|
||||||
parsedState := &parsedRespState{
|
parsedState := &parsedRespState{
|
||||||
AuthEvents: make([]*gomatrixserverlib.Event, len(state.AuthEvents)),
|
AuthEvents: authEvents,
|
||||||
StateEvents: make([]*gomatrixserverlib.Event, len(state.StateEvents)),
|
StateEvents: stateEvents,
|
||||||
}
|
}
|
||||||
// Cache the results of this state lookup and deduplicate anything we already
|
// Cache the results of this state lookup and deduplicate anything we already
|
||||||
// have in the cache, freeing up memory.
|
// have in the cache, freeing up memory.
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package input_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -12,30 +11,22 @@ import (
|
||||||
"github.com/matrix-org/dendrite/roomserver/internal/input"
|
"github.com/matrix-org/dendrite/roomserver/internal/input"
|
||||||
"github.com/matrix-org/dendrite/roomserver/storage"
|
"github.com/matrix-org/dendrite/roomserver/storage"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
|
"github.com/matrix-org/dendrite/setup/process"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/nats-io/nats.go"
|
||||||
)
|
)
|
||||||
|
|
||||||
func psqlConnectionString() config.DataSource {
|
var js nats.JetStreamContext
|
||||||
user := os.Getenv("POSTGRES_USER")
|
var jc *nats.Conn
|
||||||
if user == "" {
|
|
||||||
user = "dendrite"
|
func TestMain(m *testing.M) {
|
||||||
}
|
var pc *process.ProcessContext
|
||||||
dbName := os.Getenv("POSTGRES_DB")
|
pc, js, jc = jetstream.PrepareForTests()
|
||||||
if dbName == "" {
|
code := m.Run()
|
||||||
dbName = "dendrite"
|
pc.ShutdownDendrite()
|
||||||
}
|
pc.WaitForComponentsToFinish()
|
||||||
connStr := fmt.Sprintf(
|
os.Exit(code)
|
||||||
"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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSingleTransactionOnInput(t *testing.T) {
|
func TestSingleTransactionOnInput(t *testing.T) {
|
||||||
|
|
@ -63,7 +54,7 @@ func TestSingleTransactionOnInput(t *testing.T) {
|
||||||
}
|
}
|
||||||
db, err := storage.Open(
|
db, err := storage.Open(
|
||||||
&config.DatabaseOptions{
|
&config.DatabaseOptions{
|
||||||
ConnectionString: psqlConnectionString(),
|
ConnectionString: "",
|
||||||
MaxOpenConnections: 1,
|
MaxOpenConnections: 1,
|
||||||
MaxIdleConnections: 1,
|
MaxIdleConnections: 1,
|
||||||
},
|
},
|
||||||
|
|
@ -74,7 +65,9 @@ func TestSingleTransactionOnInput(t *testing.T) {
|
||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
}
|
}
|
||||||
inputter := &input.Inputer{
|
inputter := &input.Inputer{
|
||||||
DB: db,
|
DB: db,
|
||||||
|
JetStream: js,
|
||||||
|
NATSClient: jc,
|
||||||
}
|
}
|
||||||
res := &api.InputRoomEventsResponse{}
|
res := &api.InputRoomEventsResponse{}
|
||||||
inputter.InputRoomEvents(
|
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 == "" {
|
if request.Sender == "" {
|
||||||
var events []types.Event
|
var events []types.Event
|
||||||
var eventNIDs []types.EventNID
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("r.DB.GetMembershipEventNIDsForRoom: %w", err)
|
return fmt.Errorf("r.DB.GetMembershipEventNIDsForRoom: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ const (
|
||||||
RoomserverPerformInvitePath = "/roomserver/performInvite"
|
RoomserverPerformInvitePath = "/roomserver/performInvite"
|
||||||
RoomserverPerformPeekPath = "/roomserver/performPeek"
|
RoomserverPerformPeekPath = "/roomserver/performPeek"
|
||||||
RoomserverPerformUnpeekPath = "/roomserver/performUnpeek"
|
RoomserverPerformUnpeekPath = "/roomserver/performUnpeek"
|
||||||
|
RoomserverPerformRoomUpgradePath = "/roomserver/performRoomUpgrade"
|
||||||
RoomserverPerformJoinPath = "/roomserver/performJoin"
|
RoomserverPerformJoinPath = "/roomserver/performJoin"
|
||||||
RoomserverPerformLeavePath = "/roomserver/performLeave"
|
RoomserverPerformLeavePath = "/roomserver/performLeave"
|
||||||
RoomserverPerformBackfillPath = "/roomserver/performBackfill"
|
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(
|
func (h *httpRoomserverInternalAPI) PerformLeave(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request *api.PerformLeaveRequest,
|
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