Merge pull request #81 from globekeeper/release/upstream-v0.13.5-16

Release/upstream v0.13.5 16
This commit is contained in:
Daniel Aloni 2023-12-31 11:39:40 +02:00 committed by GitHub
commit 7e16873f2f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
126 changed files with 1844 additions and 475 deletions

View file

@ -62,6 +62,6 @@ If you can identify any relevant log snippets from server logs, please include
those (please be careful to remove any personal or private data). Please surround them with those (please be careful to remove any personal or private data). Please surround them with
``` (three backticks, on a line on their own), so that they are formatted legibly. ``` (three backticks, on a line on their own), so that they are formatted legibly.
Alternatively, please send logs to @kegan:matrix.org or @neilalexander:matrix.org Alternatively, please send logs to @kegan:matrix.org, @s7evink:matrix.org or @devonh:one.ems.host
with a link to the respective Github issue, thanks! with a link to the respective Github issue, thanks!
--> -->

View file

@ -123,7 +123,7 @@ jobs:
with: with:
# Optional: pass GITHUB_TOKEN to avoid rate limiting. # Optional: pass GITHUB_TOKEN to avoid rate limiting.
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- run: go test -json -v ./... 2>&1 | gotestfmt - run: go test -json -v ./... 2>&1 | gotestfmt -hide all
env: env:
POSTGRES_HOST: localhost POSTGRES_HOST: localhost
POSTGRES_USER: postgres POSTGRES_USER: postgres
@ -255,7 +255,7 @@ jobs:
key: ${{ runner.os }}-go-stable-test-race-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-go-stable-test-race-${{ hashFiles('**/go.sum') }}
restore-keys: | restore-keys: |
${{ runner.os }}-go-stable-test-race- ${{ runner.os }}-go-stable-test-race-
- run: go test -race -json -v -coverpkg=./... -coverprofile=cover.out $(go list ./... | grep -v /cmd/dendrite*) 2>&1 | gotestfmt - run: go test -race -json -v -coverpkg=./... -coverprofile=cover.out $(go list ./... | grep -v /cmd/dendrite*) 2>&1 | gotestfmt -hide all
env: env:
POSTGRES_HOST: localhost POSTGRES_HOST: localhost
POSTGRES_USER: postgres POSTGRES_USER: postgres
@ -280,6 +280,8 @@ jobs:
with: with:
go-version: "stable" go-version: "stable"
cache: true cache: true
- name: Docker version
run: docker version
- name: Build upgrade-tests - name: Build upgrade-tests
run: go build ./cmd/dendrite-upgrade-tests run: go build ./cmd/dendrite-upgrade-tests
- name: Test upgrade (PostgreSQL) - name: Test upgrade (PostgreSQL)
@ -298,6 +300,8 @@ jobs:
with: with:
go-version: "stable" go-version: "stable"
cache: true cache: true
- name: Docker version
run: docker version
- name: Build upgrade-tests - name: Build upgrade-tests
run: go build ./cmd/dendrite-upgrade-tests run: go build ./cmd/dendrite-upgrade-tests
- name: Test upgrade (PostgreSQL) - name: Test upgrade (PostgreSQL)
@ -421,7 +425,7 @@ jobs:
# Run Complement # Run Complement
- run: | - run: |
set -o pipefail && set -o pipefail &&
go test -v -json -tags dendrite_blacklist ./tests/... 2>&1 | gotestfmt go test -v -json -tags dendrite_blacklist ./tests ./tests/csapi 2>&1 | gotestfmt -hide all
shell: bash shell: bash
name: Run Complement Tests name: Run Complement Tests
env: env:

View file

@ -32,10 +32,6 @@ jobs:
if: github.event_name == 'release' # Only for GitHub releases if: github.event_name == 'release' # Only for GitHub releases
run: | run: |
echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
echo "BUILD=$(git rev-parse --short HEAD || \"\")" >> $GITHUB_ENV
BRANCH=$(git symbolic-ref --short HEAD | tr -d \/)
[ ${BRANCH} == "main" ] && BRANCH=""
echo "BRANCH=${BRANCH}" >> $GITHUB_ENV
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx - name: Set up Docker Buildx
@ -57,10 +53,9 @@ jobs:
id: docker_build_monolith id: docker_build_monolith
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
with: with:
cache-from: type=gha cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:buildcache
cache-to: type=gha,mode=max cache-to: type=registry,ref=ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:buildcache,mode=max
context: . context: .
build-args: FLAGS=-X github.com/matrix-org/dendrite/internal.branch=${{ env.BRANCH }} -X github.com/matrix-org/dendrite/internal.build=${{ env.BUILD }}
platforms: ${{ env.PLATFORMS }} platforms: ${{ env.PLATFORMS }}
push: true push: true
tags: | tags: |
@ -75,7 +70,6 @@ jobs:
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
context: . context: .
build-args: FLAGS=-X github.com/matrix-org/dendrite/internal.branch=${{ env.BRANCH }} -X github.com/matrix-org/dendrite/internal.build=${{ env.BUILD }}
platforms: ${{ env.PLATFORMS }} platforms: ${{ env.PLATFORMS }}
push: true push: true
tags: | tags: |
@ -109,10 +103,6 @@ jobs:
if: github.event_name == 'release' # Only for GitHub releases if: github.event_name == 'release' # Only for GitHub releases
run: | run: |
echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
echo "BUILD=$(git rev-parse --short HEAD || \"\")" >> $GITHUB_ENV
BRANCH=$(git symbolic-ref --short HEAD | tr -d \/)
[ ${BRANCH} == "main" ] && BRANCH=""
echo "BRANCH=${BRANCH}" >> $GITHUB_ENV
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx - name: Set up Docker Buildx
@ -137,7 +127,6 @@ jobs:
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
context: . context: .
build-args: FLAGS=-X github.com/matrix-org/dendrite/internal.branch=${{ env.BRANCH }} -X github.com/matrix-org/dendrite/internal.build=${{ env.BUILD }}
file: ./build/docker/Dockerfile.demo-pinecone file: ./build/docker/Dockerfile.demo-pinecone
platforms: ${{ env.PLATFORMS }} platforms: ${{ env.PLATFORMS }}
push: true push: true
@ -153,7 +142,6 @@ jobs:
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
context: . context: .
build-args: FLAGS=-X github.com/matrix-org/dendrite/internal.branch=${{ env.BRANCH }} -X github.com/matrix-org/dendrite/internal.build=${{ env.BUILD }}
file: ./build/docker/Dockerfile.demo-pinecone file: ./build/docker/Dockerfile.demo-pinecone
platforms: ${{ env.PLATFORMS }} platforms: ${{ env.PLATFORMS }}
push: true push: true
@ -176,10 +164,6 @@ jobs:
if: github.event_name == 'release' # Only for GitHub releases if: github.event_name == 'release' # Only for GitHub releases
run: | run: |
echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
echo "BUILD=$(git rev-parse --short HEAD || \"\")" >> $GITHUB_ENV
BRANCH=$(git symbolic-ref --short HEAD | tr -d \/)
[ ${BRANCH} == "main" ] && BRANCH=""
echo "BRANCH=${BRANCH}" >> $GITHUB_ENV
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx - name: Set up Docker Buildx
@ -204,7 +188,6 @@ jobs:
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
context: . context: .
build-args: FLAGS=-X github.com/matrix-org/dendrite/internal.branch=${{ env.BRANCH }} -X github.com/matrix-org/dendrite/internal.build=${{ env.BUILD }}
file: ./build/docker/Dockerfile.demo-yggdrasil file: ./build/docker/Dockerfile.demo-yggdrasil
platforms: ${{ env.PLATFORMS }} platforms: ${{ env.PLATFORMS }}
push: true push: true
@ -220,7 +203,6 @@ jobs:
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
context: . context: .
build-args: FLAGS=-X github.com/matrix-org/dendrite/internal.branch=${{ env.BRANCH }} -X github.com/matrix-org/dendrite/internal.build=${{ env.BUILD }}
file: ./build/docker/Dockerfile.demo-yggdrasil file: ./build/docker/Dockerfile.demo-yggdrasil
platforms: ${{ env.PLATFORMS }} platforms: ${{ env.PLATFORMS }}
push: true push: true

View file

@ -32,7 +32,7 @@ jobs:
version: v3.10.0 version: v3.10.0
- name: Run chart-releaser - name: Run chart-releaser
uses: helm/chart-releaser-action@v1.4.1 uses: helm/chart-releaser-action@ed43eb303604cbc0eeec8390544f7748dc6c790d # specific commit, since `mark_as_latest` is not yet in a release
env: env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
with: with:

View file

@ -40,52 +40,53 @@ jobs:
run: ct lint --config helm/ct.yaml run: ct lint --config helm/ct.yaml
# only bother to run if lint step reports a change to the helm chart # only bother to run if lint step reports a change to the helm chart
install: # GlobeKeeper: Skipping this as it not needed for now and it is failing due to env.
needs: # install:
- lint # needs:
if: ${{ needs.lint.outputs.changed == 'true' }} # - lint
name: Install Helm charts # if: ${{ needs.lint.outputs.changed == 'true' }}
runs-on: ubuntu-latest # name: Install Helm charts
steps: # runs-on: ubuntu-latest
- name: Checkout # steps:
uses: actions/checkout@v3 # - name: Checkout
with: # uses: actions/checkout@v3
fetch-depth: 0 # with:
ref: ${{ inputs.checkoutCommit }} # fetch-depth: 0
- name: Install Kubernetes tools # ref: ${{ inputs.checkoutCommit }}
uses: yokawasa/action-setup-kube-tools@v0.8.2 # - name: Install Kubernetes tools
with: # uses: yokawasa/action-setup-kube-tools@v0.8.2
setup-tools: | # with:
helmv3 # setup-tools: |
helm: "3.10.3" # helmv3
- uses: actions/setup-python@v4 # helm: "3.10.3"
with: # - uses: actions/setup-python@v4
python-version: "3.10" # with:
- name: Set up chart-testing # python-version: "3.10"
uses: helm/chart-testing-action@v2.3.1 # - name: Set up chart-testing
- name: Create k3d cluster # uses: helm/chart-testing-action@v2.3.1
uses: nolar/setup-k3d-k3s@v1 # - name: Create k3d cluster
with: # uses: nolar/setup-k3d-k3s@v1
version: v1.21 # with:
- name: Remove node taints # version: v1.21
run: | # - name: Remove node taints
kubectl taint --all=true nodes node.cloudprovider.kubernetes.io/uninitialized- || true # run: |
- name: Run chart-testing (install) # kubectl taint --all=true nodes node.cloudprovider.kubernetes.io/uninitialized- || true
run: ct install --config helm/ct.yaml # - name: Run chart-testing (install)
# run: ct install --config helm/ct.yaml
# Install the chart using helm directly and test with create-account # # Install the chart using helm directly and test with create-account
- name: Install chart # - name: Install chart
run: | # run: |
helm install --values helm/dendrite/ci/ct-postgres-sharedsecret-values.yaml dendrite helm/dendrite # helm install --values helm/dendrite/ci/ct-postgres-sharedsecret-values.yaml dendrite helm/dendrite
- name: Wait for Postgres and Dendrite to be up # - name: Wait for Postgres and Dendrite to be up
run: | # run: |
kubectl wait --for=condition=ready --timeout=90s pod -l app.kubernetes.io/name=postgresql || kubectl get pods -A # kubectl wait --for=condition=ready --timeout=90s pod -l app.kubernetes.io/name=postgresql || kubectl get pods -A
kubectl wait --for=condition=ready --timeout=90s pod -l app.kubernetes.io/name=dendrite || kubectl get pods -A # kubectl wait --for=condition=ready --timeout=90s pod -l app.kubernetes.io/name=dendrite || kubectl get pods -A
kubectl get pods -A # kubectl get pods -A
kubectl get services # kubectl get services
kubectl get ingress # kubectl get ingress
kubectl logs -l app.kubernetes.io/name=dendrite # kubectl logs -l app.kubernetes.io/name=dendrite
- name: Run create account # - name: Run create account
run: | # run: |
podName=$(kubectl get pods -l app.kubernetes.io/name=dendrite -o name) # podName=$(kubectl get pods -l app.kubernetes.io/name=dendrite -o name)
kubectl exec "${podName}" -- /usr/bin/create-account -username alice -password somerandompassword # kubectl exec "${podName}" -- /usr/bin/create-account -username alice -password somerandompassword

View file

@ -128,7 +128,7 @@ jobs:
# See https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2004-Readme.md specifically GOROOT_1_17_X64 # See https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2004-Readme.md specifically GOROOT_1_17_X64
run: | run: |
sudo apt-get update && sudo apt-get install -y libolm3 libolm-dev sudo apt-get update && sudo apt-get install -y libolm3 libolm-dev
go get -v github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest go install github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest
- name: Run actions/checkout@v3 for dendrite - name: Run actions/checkout@v3 for dendrite
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:

View file

@ -1,5 +1,29 @@
# Changelog # Changelog
## Dendrite 0.13.5 (2023-12-12)
Upgrading to this version is **highly** recommended, as it fixes several long-standing bugs in
our CanonicalJSON implementation.
### Fixes
- Convert unicode escapes to lowercase (gomatrixserverlib)
- Fix canonical json utf-16 surrogate pair detection logic (gomatrixserverlib)
- Handle negative zero and exponential numbers in Canonical JSON verification (gomatrixserverlib)
- Avoid logging unnecessary messages when unable to fetch server keys if multiple fetchers are used (gomatrixserverlib)
- Issues around the device list updater have been fixed, which should ensure that there are always
workers available to process incoming device list updates.
- A panic in the `/hierarchy` endpoints used for spaces has been fixed (client-server and server-server API)
- Fixes around the way we handle database transactions (including a potential connection leak)
- ACLs are now updated when received as outliers
- A race condition, which could lead to bridges instantly leaving a room after joining it, between the SyncAPI and
Appservices has been fixed
### Features
- **Appservice login is now supported!**
- Users can now kick themselves (used by some bridges)
## Dendrite 0.13.4 (2023-10-25) ## Dendrite 0.13.4 (2023-10-25)
Upgrading to this version is **highly** recommended, as it fixes a long-standing bug in the state resolution Upgrading to this version is **highly** recommended, as it fixes a long-standing bug in the state resolution

View file

@ -1,9 +1,12 @@
#syntax=docker/dockerfile:1.2
# #
# base installs required dependencies and runs go mod download to cache dependencies # base installs required dependencies and runs go mod download to cache dependencies
# #
# Pinned to alpine3.18 until https://github.com/mattn/go-sqlite3/issues/1164 is solved
ARG BUILDPLATFORM=${BUILDPLATFORM} ARG BUILDPLATFORM=${BUILDPLATFORM}
FROM --platform=$BUILDPLATFORM docker.io/golang:1.21-alpine AS base FROM --platform=$BUILDPLATFORM docker.io/golang:1.21-alpine3.18 AS base
RUN apk --update --no-cache add bash build-base curl RUN apk --update --no-cache add bash build-base curl git
# #
# build creates all needed binaries # build creates all needed binaries
@ -12,8 +15,9 @@ FROM --platform=$BUILDPLATFORM base AS build
WORKDIR /src WORKDIR /src
ARG TARGETOS ARG TARGETOS
ARG TARGETARCH ARG TARGETARCH
ARG FLAGS
# Mount volumes using the -v flag instead of --mount to avoid requiring BuildKit which is not easily supported using cloudbuild. # Not mounting volumes using the --mount flag to avoid requiring BuildKit which is not easily supported using cloudbuild.
COPY . . COPY . .
RUN mkdir -p /root/.cache/go-build && \ RUN mkdir -p /root/.cache/go-build && \
mkdir -p /go/pkg/mod mkdir -p /go/pkg/mod
@ -22,10 +26,10 @@ VOLUME /go/pkg/mod
# Run the build command in multiple RUN commands # Run the build command in multiple RUN commands
RUN USERARCH=`go env GOARCH` RUN USERARCH=`go env GOARCH`
ENV GOARCH="$TARGETARCH" RUN GOARCH="$TARGETARCH"
ENV GOOS="linux" RUN GOOS="linux"
RUN CGO_ENABLED=$([ "$TARGETARCH" = "$USERARCH" ] && echo "1" || echo "0") RUN CGO_ENABLED=$([ "$TARGETARCH" = "$USERARCH" ] && echo "1" || echo "0")
RUN go build -v -trimpath -o /out/ ./cmd/... RUN go build -v -ldflags="${FLAGS}" -trimpath -o /out/ ./cmd/...
# #
# Builds the Dendrite image containing all required binaries # Builds the Dendrite image containing all required binaries
@ -36,8 +40,8 @@ LABEL org.opencontainers.image.title="Dendrite"
LABEL org.opencontainers.image.description="Next-generation Matrix homeserver written in Go" LABEL org.opencontainers.image.description="Next-generation Matrix homeserver written in Go"
LABEL org.opencontainers.image.source="https://github.com/matrix-org/dendrite" LABEL org.opencontainers.image.source="https://github.com/matrix-org/dendrite"
LABEL org.opencontainers.image.licenses="Apache-2.0" LABEL org.opencontainers.image.licenses="Apache-2.0"
LABEL org.opencontainers.image.vendor="The Matrix.org Foundation C.I.C."
LABEL org.opencontainers.image.documentation="https://matrix-org.github.io/dendrite/" LABEL org.opencontainers.image.documentation="https://matrix-org.github.io/dendrite/"
LABEL org.opencontainers.image.vendor="The Matrix.org Foundation C.I.C."
COPY --from=build /out/create-account /usr/bin/create-account COPY --from=build /out/create-account /usr/bin/create-account
COPY --from=build /out/generate-config /usr/bin/generate-config COPY --from=build /out/generate-config /usr/bin/generate-config

View file

@ -14,7 +14,18 @@ import (
"testing" "testing"
"time" "time"
"github.com/matrix-org/dendrite/clientapi"
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/federationapi/statistics"
"github.com/matrix-org/dendrite/internal/httputil"
"github.com/matrix-org/dendrite/roomserver/types"
"github.com/matrix-org/dendrite/syncapi"
uapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util"
"github.com/nats-io/nats.go"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tidwall/gjson"
"github.com/matrix-org/dendrite/appservice" "github.com/matrix-org/dendrite/appservice"
"github.com/matrix-org/dendrite/appservice/api" "github.com/matrix-org/dendrite/appservice/api"
@ -32,6 +43,10 @@ import (
"github.com/matrix-org/dendrite/test/testrig" "github.com/matrix-org/dendrite/test/testrig"
) )
var testIsBlacklistedOrBackingOff = func(s spec.ServerName) (*statistics.ServerStatistics, error) {
return &statistics.ServerStatistics{}, nil
}
func TestAppserviceInternalAPI(t *testing.T) { func TestAppserviceInternalAPI(t *testing.T) {
// Set expected results // Set expected results
@ -144,7 +159,7 @@ func TestAppserviceInternalAPI(t *testing.T) {
cm := sqlutil.NewConnectionManager(ctx, cfg.Global.DatabaseOptions) cm := sqlutil.NewConnectionManager(ctx, cfg.Global.DatabaseOptions)
rsAPI := roomserver.NewInternalAPI(ctx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(ctx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
usrAPI := userapi.NewInternalAPI(ctx, cfg, cm, &natsInstance, rsAPI, nil) usrAPI := userapi.NewInternalAPI(ctx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
asAPI := appservice.NewInternalAPI(ctx, cfg, &natsInstance, usrAPI, rsAPI) asAPI := appservice.NewInternalAPI(ctx, cfg, &natsInstance, usrAPI, rsAPI)
runCases(t, asAPI) runCases(t, asAPI)
@ -239,7 +254,7 @@ func TestAppserviceInternalAPI_UnixSocket_Simple(t *testing.T) {
cm := sqlutil.NewConnectionManager(ctx, cfg.Global.DatabaseOptions) cm := sqlutil.NewConnectionManager(ctx, cfg.Global.DatabaseOptions)
rsAPI := roomserver.NewInternalAPI(ctx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(ctx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
usrAPI := userapi.NewInternalAPI(ctx, cfg, cm, &natsInstance, rsAPI, nil) usrAPI := userapi.NewInternalAPI(ctx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
asAPI := appservice.NewInternalAPI(ctx, cfg, &natsInstance, usrAPI, rsAPI) asAPI := appservice.NewInternalAPI(ctx, cfg, &natsInstance, usrAPI, rsAPI)
t.Run("UserIDExists", func(t *testing.T) { t.Run("UserIDExists", func(t *testing.T) {
@ -378,7 +393,7 @@ func TestRoomserverConsumerOneInvite(t *testing.T) {
// Create required internal APIs // Create required internal APIs
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
usrAPI := userapi.NewInternalAPI(processCtx, cfg, cm, natsInstance, rsAPI, nil) usrAPI := userapi.NewInternalAPI(processCtx, cfg, cm, natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
// start the consumer // start the consumer
appservice.NewInternalAPI(processCtx, cfg, natsInstance, usrAPI, rsAPI) appservice.NewInternalAPI(processCtx, cfg, natsInstance, usrAPI, rsAPI)
@ -402,3 +417,190 @@ func TestRoomserverConsumerOneInvite(t *testing.T) {
close(evChan) close(evChan)
}) })
} }
// Note: If this test panics, it is because we timed out waiting for the
// join event to come through to the appservice and we close the DB/shutdown Dendrite. This makes the
// syncAPI unhappy, as it is unable to write to the database.
func TestOutputAppserviceEvent(t *testing.T) {
alice := test.NewUser(t)
bob := test.NewUser(t)
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
cfg, processCtx, closeDB := testrig.CreateConfig(t, dbType)
defer closeDB()
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
natsInstance := &jetstream.NATSInstance{}
evChan := make(chan struct{})
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
// Create required internal APIs
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil)
// Create the router, so we can hit `/joined_members`
routers := httputil.NewRouters()
accessTokens := map[*test.User]userDevice{
bob: {},
}
usrAPI := userapi.NewInternalAPI(processCtx, cfg, cm, natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
clientapi.AddPublicRoutes(processCtx, routers, cfg, natsInstance, nil, rsAPI, nil, nil, nil, usrAPI, nil, nil, caching.DisableMetrics)
createAccessTokens(t, accessTokens, usrAPI, processCtx.Context(), routers)
room := test.NewRoom(t, alice)
// Invite Bob
room.CreateAndInsert(t, alice, spec.MRoomMember, map[string]interface{}{
"membership": "invite",
}, test.WithStateKey(bob.ID))
// create a dummy AS url, handling the events
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var txn consumers.ApplicationServiceTransaction
err := json.NewDecoder(r.Body).Decode(&txn)
if err != nil {
t.Fatal(err)
}
for _, ev := range txn.Events {
if ev.Type != spec.MRoomMember {
continue
}
if ev.StateKey != nil && *ev.StateKey == bob.ID {
membership := gjson.GetBytes(ev.Content, "membership").Str
t.Logf("Processing membership: %s", membership)
switch membership {
case spec.Invite:
// Accept the invite
joinEv := room.CreateAndInsert(t, bob, spec.MRoomMember, map[string]interface{}{
"membership": "join",
}, test.WithStateKey(bob.ID))
if err := rsapi.SendEvents(context.Background(), rsAPI, rsapi.KindNew, []*types.HeaderedEvent{joinEv}, "test", "test", "test", nil, false); err != nil {
t.Fatalf("failed to send events: %v", err)
}
case spec.Join: // the AS has received the join event, now hit `/joined_members` to validate that
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/_matrix/client/v3/rooms/"+room.ID+"/joined_members", nil)
req.Header.Set("Authorization", "Bearer "+accessTokens[bob].accessToken)
routers.Client.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected HTTP 200, got %d: %s", rec.Code, rec.Body.String())
}
// Both Alice and Bob should be joined. If not, we have a race condition
if !gjson.GetBytes(rec.Body.Bytes(), "joined."+alice.ID).Exists() {
t.Errorf("Alice is not joined to the room") // in theory should not happen
}
if !gjson.GetBytes(rec.Body.Bytes(), "joined."+bob.ID).Exists() {
t.Errorf("Bob is not joined to the room")
}
evChan <- struct{}{}
default:
t.Fatalf("Unexpected membership: %s", membership)
}
}
}
}))
defer srv.Close()
as := &config.ApplicationService{
ID: "someID",
URL: srv.URL,
ASToken: "",
HSToken: "",
SenderLocalpart: "senderLocalPart",
NamespaceMap: map[string][]config.ApplicationServiceNamespace{
"users": {{RegexpObject: regexp.MustCompile(bob.ID)}},
"aliases": {{RegexpObject: regexp.MustCompile(room.ID)}},
},
}
as.CreateHTTPClient(cfg.AppServiceAPI.DisableTLSValidation)
// Create a dummy application service
cfg.AppServiceAPI.Derived.ApplicationServices = []config.ApplicationService{*as}
// Prepare AS Streams on the old topic to validate that they get deleted
jsCtx, _ := natsInstance.Prepare(processCtx, &cfg.Global.JetStream)
token := jetstream.Tokenise(as.ID)
if err := jetstream.JetStreamConsumer(
processCtx.Context(), jsCtx, cfg.Global.JetStream.Prefixed(jetstream.OutputRoomEvent),
cfg.Global.JetStream.Durable("Appservice_"+token),
50, // maximum number of events to send in a single transaction
func(ctx context.Context, msgs []*nats.Msg) bool {
return true
},
); err != nil {
t.Fatal(err)
}
// Start the syncAPI to have `/joined_members` available
syncapi.AddPublicRoutes(processCtx, routers, cfg, cm, natsInstance, usrAPI, rsAPI, caches, caching.DisableMetrics)
// start the consumer
appservice.NewInternalAPI(processCtx, cfg, natsInstance, usrAPI, rsAPI)
// At this point, the old JetStream consumers should be deleted
for consumer := range jsCtx.Consumers(cfg.Global.JetStream.Prefixed(jetstream.OutputRoomEvent)) {
if consumer.Name == cfg.Global.JetStream.Durable("Appservice_"+token)+"Pull" {
t.Fatalf("Consumer still exists")
}
}
// Create the room, this triggers the AS to receive an invite for Bob.
if err := rsapi.SendEvents(context.Background(), rsAPI, rsapi.KindNew, room.Events(), "test", "test", "test", nil, false); err != nil {
t.Fatalf("failed to send events: %v", err)
}
select {
// Pretty generous timeout duration...
case <-time.After(time.Millisecond * 1000): // wait for the AS to process the events
t.Errorf("Timed out waiting for join event")
case <-evChan:
}
close(evChan)
})
}
type userDevice struct {
accessToken string
deviceID string
password string
}
func createAccessTokens(t *testing.T, accessTokens map[*test.User]userDevice, userAPI uapi.UserInternalAPI, ctx context.Context, routers httputil.Routers) {
t.Helper()
for u := range accessTokens {
localpart, serverName, _ := gomatrixserverlib.SplitID('@', u.ID)
userRes := &uapi.PerformAccountCreationResponse{}
password := util.RandomString(8)
if err := userAPI.PerformAccountCreation(ctx, &uapi.PerformAccountCreationRequest{
AccountType: u.AccountType,
Localpart: localpart,
ServerName: serverName,
Password: password,
}, userRes); err != nil {
t.Errorf("failed to create account: %s", err)
}
req := test.NewRequest(t, http.MethodPost, "/_matrix/client/v3/login", test.WithJSONBody(t, map[string]interface{}{
"type": authtypes.LoginTypePassword,
"identifier": map[string]interface{}{
"type": "m.id.user",
"user": u.ID,
},
"password": password,
}))
rec := httptest.NewRecorder()
routers.Client.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("failed to login: %s", rec.Body.String())
}
accessTokens[u] = userDevice{
accessToken: gjson.GetBytes(rec.Body.Bytes(), "access_token").String(),
deviceID: gjson.GetBytes(rec.Body.Bytes(), "device_id").String(),
password: password,
}
}
}

View file

@ -71,13 +71,14 @@ func NewOutputRoomEventConsumer(
ctx: process.Context(), ctx: process.Context(),
cfg: cfg, cfg: cfg,
jetstream: js, jetstream: js,
topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputRoomEvent), topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputAppserviceEvent),
rsAPI: rsAPI, rsAPI: rsAPI,
} }
} }
// Start consuming from room servers // Start consuming from room servers
func (s *OutputRoomEventConsumer) Start() error { func (s *OutputRoomEventConsumer) Start() error {
durableNames := make([]string, 0, len(s.cfg.Derived.ApplicationServices))
for _, as := range s.cfg.Derived.ApplicationServices { for _, as := range s.cfg.Derived.ApplicationServices {
appsvc := as appsvc := as
state := &appserviceState{ state := &appserviceState{
@ -95,6 +96,15 @@ func (s *OutputRoomEventConsumer) Start() error {
); err != nil { ); err != nil {
return fmt.Errorf("failed to create %q consumer: %w", token, err) return fmt.Errorf("failed to create %q consumer: %w", token, err)
} }
durableNames = append(durableNames, s.cfg.Matrix.JetStream.Durable("Appservice_"+token))
}
// Cleanup any consumers still existing on the OutputRoomEvent stream
// to avoid messages not being deleted
for _, consumerName := range durableNames {
err := s.jetstream.DeleteConsumer(s.cfg.Matrix.JetStream.Prefixed(jetstream.OutputRoomEvent), consumerName+"Pull")
if err != nil && err != nats.ErrConsumerNotFound {
return err
}
} }
return nil return nil
} }

View file

@ -216,7 +216,7 @@ func (m *DendriteMonolith) Start() {
processCtx, cfg, cm, &natsInstance, federation, rsAPI, caches, keyRing, true, processCtx, cfg, cm, &natsInstance, federation, rsAPI, caches, keyRing, true,
) )
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federation) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federation, caching.EnableMetrics, fsAPI.IsBlacklistedOrBackingOff)
asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI) asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI)
rsAPI.SetAppserviceAPI(asAPI) rsAPI.SetAppserviceAPI(asAPI)

View file

@ -45,7 +45,7 @@ func TestAdminCreateToken(t *testing.T) {
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics) caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
accessTokens := map[*test.User]userDevice{ accessTokens := map[*test.User]userDevice{
aliceAdmin: {}, aliceAdmin: {},
@ -196,7 +196,7 @@ func TestAdminListRegistrationTokens(t *testing.T) {
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics) caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
accessTokens := map[*test.User]userDevice{ accessTokens := map[*test.User]userDevice{
aliceAdmin: {}, aliceAdmin: {},
@ -314,7 +314,7 @@ func TestAdminGetRegistrationToken(t *testing.T) {
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics) caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
accessTokens := map[*test.User]userDevice{ accessTokens := map[*test.User]userDevice{
aliceAdmin: {}, aliceAdmin: {},
@ -415,7 +415,7 @@ func TestAdminDeleteRegistrationToken(t *testing.T) {
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics) caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
accessTokens := map[*test.User]userDevice{ accessTokens := map[*test.User]userDevice{
aliceAdmin: {}, aliceAdmin: {},
@ -509,7 +509,7 @@ func TestAdminUpdateRegistrationToken(t *testing.T) {
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics) caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
accessTokens := map[*test.User]userDevice{ accessTokens := map[*test.User]userDevice{
aliceAdmin: {}, aliceAdmin: {},
@ -693,7 +693,7 @@ func TestAdminResetPassword(t *testing.T) {
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
// Needed for changing the password/login // Needed for changing the password/login
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
// We mostly need the userAPI for this test, so nil for other APIs/caches etc. // We mostly need the userAPI for this test, so nil for other APIs/caches etc.
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
@ -791,7 +791,7 @@ func TestPurgeRoom(t *testing.T) {
fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, rsAPI, caches, nil, true) fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, rsAPI, caches, nil, true)
rsAPI.SetFederationAPI(fsAPI, nil) rsAPI.SetFederationAPI(fsAPI, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
syncapi.AddPublicRoutes(processCtx, routers, cfg, cm, &natsInstance, userAPI, rsAPI, caches, caching.DisableMetrics) syncapi.AddPublicRoutes(processCtx, routers, cfg, cm, &natsInstance, userAPI, rsAPI, caches, caching.DisableMetrics)
// Create the room // Create the room
@ -863,7 +863,7 @@ func TestAdminEvacuateRoom(t *testing.T) {
fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, rsAPI, caches, nil, true) fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, rsAPI, caches, nil, true)
rsAPI.SetFederationAPI(fsAPI, nil) rsAPI.SetFederationAPI(fsAPI, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
// Create the room // Create the room
if err := api.SendEvents(ctx, rsAPI, api.KindNew, room.Events(), "test", "test", api.DoNotSendToOtherServers, nil, false); err != nil { if err := api.SendEvents(ctx, rsAPI, api.KindNew, room.Events(), "test", "test", api.DoNotSendToOtherServers, nil, false); err != nil {
@ -964,7 +964,7 @@ func TestAdminEvacuateUser(t *testing.T) {
fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, basepkg.CreateFederationClient(cfg, nil), rsAPI, caches, nil, true) fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, basepkg.CreateFederationClient(cfg, nil), rsAPI, caches, nil, true)
rsAPI.SetFederationAPI(fsAPI, nil) rsAPI.SetFederationAPI(fsAPI, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
// Create the room // Create the room
if err := api.SendEvents(ctx, rsAPI, api.KindNew, room.Events(), "test", "test", api.DoNotSendToOtherServers, nil, false); err != nil { if err := api.SendEvents(ctx, rsAPI, api.KindNew, room.Events(), "test", "test", api.DoNotSendToOtherServers, nil, false); err != nil {
@ -1055,7 +1055,7 @@ func TestAdminMarkAsStale(t *testing.T) {
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions) cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc. // We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)

View file

@ -15,7 +15,6 @@
package auth package auth
import ( import (
"context"
"encoding/json" "encoding/json"
"io" "io"
"net/http" "net/http"
@ -33,8 +32,18 @@ import (
// called after authorization has completed, with the result of the authorization. // called after authorization has completed, with the result of the authorization.
// If the final return value is non-nil, an error occurred and the cleanup function // If the final return value is non-nil, an error occurred and the cleanup function
// is nil. // is nil.
func LoginFromJSONReader(ctx context.Context, r io.Reader, useraccountAPI uapi.ClientUserAPI, cfg *config.ClientAPI, rt *ratelimit.RtFailedLogin) (*Login, LoginCleanupFunc, *util.JSONResponse) { // func LoginFromJSONReader(ctx context.Context, r io.Reader,
reqBytes, err := io.ReadAll(r) //
// useraccountAPI uapi.ClientUserAPI,
// cfg *config.ClientAPI, rt *ratelimit.RtFailedLogin) (*Login, LoginCleanupFunc, *util.JSONResponse) {
func LoginFromJSONReader(
req *http.Request,
useraccountAPI uapi.ClientUserAPI,
userAPI UserInternalAPIForLogin,
cfg *config.ClientAPI,
rt *ratelimit.RtFailedLogin,
) (*Login, LoginCleanupFunc, *util.JSONResponse) {
reqBytes, err := io.ReadAll(req.Body)
if err != nil { if err != nil {
err := &util.JSONResponse{ err := &util.JSONResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -63,13 +72,26 @@ func LoginFromJSONReader(ctx context.Context, r io.Reader, useraccountAPI uapi.C
Config: cfg, Config: cfg,
Rt: rt, Rt: rt,
InhibitDevice: header.InhibitDevice, InhibitDevice: header.InhibitDevice,
UserLoginAPI: useraccountAPI,
} }
case authtypes.LoginTypeToken: case authtypes.LoginTypeToken:
typ = &LoginTypeToken{ typ = &LoginTypeToken{
UserAPI: useraccountAPI, UserAPI: useraccountAPI,
Config: cfg, Config: cfg,
} }
case authtypes.LoginTypeApplicationService:
token, err := ExtractAccessToken(req)
if err != nil {
err := &util.JSONResponse{
Code: http.StatusForbidden,
JSON: spec.MissingToken(err.Error()),
}
return nil, nil, err
}
typ = &LoginTypeApplicationService{
Config: cfg,
Token: token,
}
case authtypes.LoginTypeJwt: case authtypes.LoginTypeJwt:
typ = &LoginTypeTokenJwt{ typ = &LoginTypeTokenJwt{
Config: cfg, Config: cfg,
@ -82,7 +104,7 @@ func LoginFromJSONReader(ctx context.Context, r io.Reader, useraccountAPI uapi.C
return nil, nil, &err return nil, nil, &err
} }
return typ.LoginFromJSON(ctx, reqBytes) return typ.LoginFromJSON(req.Context(), reqBytes)
} }
// UserInternalAPIForLogin contains the aspects of UserAPI required for logging in. // UserInternalAPIForLogin contains the aspects of UserAPI required for logging in.

View file

@ -0,0 +1,55 @@
// Copyright 2023 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 auth
import (
"context"
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/clientapi/httputil"
"github.com/matrix-org/dendrite/internal"
"github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/util"
)
// LoginTypeApplicationService describes how to authenticate as an
// application service
type LoginTypeApplicationService struct {
Config *config.ClientAPI
Token string
}
// Name implements Type
func (t *LoginTypeApplicationService) Name() string {
return authtypes.LoginTypeApplicationService
}
// LoginFromJSON implements Type
func (t *LoginTypeApplicationService) LoginFromJSON(
ctx context.Context, reqBytes []byte,
) (*Login, LoginCleanupFunc, *util.JSONResponse) {
var r Login
if err := httputil.UnmarshalJSON(reqBytes, &r); err != nil {
return nil, nil, err
}
_, err := internal.ValidateApplicationServiceRequest(t.Config, r.Identifier.User, t.Token)
if err != nil {
return nil, nil, err
}
cleanup := func(ctx context.Context, j *util.JSONResponse) {}
return &r, cleanup, nil
}

View file

@ -17,7 +17,9 @@ package auth
import ( import (
"context" "context"
"net/http" "net/http"
"net/http/httptest"
"reflect" "reflect"
"regexp"
"strings" "strings"
"testing" "testing"
@ -34,8 +36,9 @@ func TestLoginFromJSONReader(t *testing.T) {
ctx := context.Background() ctx := context.Background()
tsts := []struct { tsts := []struct {
Name string Name string
Body string Body string
Token string
WantUsername string WantUsername string
WantDeviceID string WantDeviceID string
@ -63,6 +66,30 @@ func TestLoginFromJSONReader(t *testing.T) {
WantDeviceID: "adevice", WantDeviceID: "adevice",
WantDeletedTokens: []string{"atoken"}, WantDeletedTokens: []string{"atoken"},
}, },
{
Name: "appServiceWorksUserID",
Body: `{
"type": "m.login.application_service",
"identifier": { "type": "m.id.user", "user": "@alice:example.com" },
"device_id": "adevice"
}`,
Token: "astoken",
WantUsername: "@alice:example.com",
WantDeviceID: "adevice",
},
{
Name: "appServiceWorksLocalpart",
Body: `{
"type": "m.login.application_service",
"identifier": { "type": "m.id.user", "user": "alice" },
"device_id": "adevice"
}`,
Token: "astoken",
WantUsername: "alice",
WantDeviceID: "adevice",
},
} }
for _, tst := range tsts { for _, tst := range tsts {
t.Run(tst.Name, func(t *testing.T) { t.Run(tst.Name, func(t *testing.T) {
@ -73,14 +100,38 @@ func TestLoginFromJSONReader(t *testing.T) {
ServerName: serverName, ServerName: serverName,
}, },
}, },
Derived: &config.Derived{
ApplicationServices: []config.ApplicationService{
{
ID: "anapplicationservice",
ASToken: "astoken",
NamespaceMap: map[string][]config.ApplicationServiceNamespace{
"users": {
{
Exclusive: true,
Regex: "@alice:example.com",
RegexpObject: regexp.MustCompile("@alice:example.com"),
},
},
},
},
},
},
RtFailedLogin: ratelimit.RtFailedLoginConfig{ RtFailedLogin: ratelimit.RtFailedLoginConfig{
Enabled: false, Enabled: false,
}, },
} }
login, cleanup, err := LoginFromJSONReader(ctx, strings.NewReader(tst.Body), &userAPI, cfg, nil)
if err != nil { req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(tst.Body))
t.Fatalf("LoginFromJSONReader failed: %+v", err) if tst.Token != "" {
req.Header.Add("Authorization", "Bearer "+tst.Token)
} }
login, cleanup, jsonErr := LoginFromJSONReader(req, &userAPI, &userAPI, cfg, nil)
if jsonErr != nil {
t.Fatalf("LoginFromJSONReader failed: %+v", jsonErr)
}
cleanup(ctx, &util.JSONResponse{Code: http.StatusOK}) cleanup(ctx, &util.JSONResponse{Code: http.StatusOK})
if login.Username() != tst.WantUsername { if login.Username() != tst.WantUsername {
@ -108,8 +159,9 @@ func TestBadLoginFromJSONReader(t *testing.T) {
ctx := context.Background() ctx := context.Background()
tsts := []struct { tsts := []struct {
Name string Name string
Body string Body string
Token string
WantErrCode spec.MatrixErrorCode WantErrCode spec.MatrixErrorCode
}{ }{
@ -146,6 +198,45 @@ func TestBadLoginFromJSONReader(t *testing.T) {
}`, }`,
WantErrCode: spec.ErrorInvalidParam, WantErrCode: spec.ErrorInvalidParam,
}, },
{
Name: "noASToken",
Body: `{
"type": "m.login.application_service",
"identifier": { "type": "m.id.user", "user": "@alice:example.com" },
"device_id": "adevice"
}`,
WantErrCode: "M_MISSING_TOKEN",
},
{
Name: "badASToken",
Token: "badastoken",
Body: `{
"type": "m.login.application_service",
"identifier": { "type": "m.id.user", "user": "@alice:example.com" },
"device_id": "adevice"
}`,
WantErrCode: "M_UNKNOWN_TOKEN",
},
{
Name: "badASNamespace",
Token: "astoken",
Body: `{
"type": "m.login.application_service",
"identifier": { "type": "m.id.user", "user": "@bob:example.com" },
"device_id": "adevice"
}`,
WantErrCode: "M_EXCLUSIVE",
},
{
Name: "badASUserID",
Token: "astoken",
Body: `{
"type": "m.login.application_service",
"identifier": { "type": "m.id.user", "user": "@alice:wrong.example.com" },
"device_id": "adevice"
}`,
WantErrCode: "M_INVALID_USERNAME",
},
} }
for _, tst := range tsts { for _, tst := range tsts {
t.Run(tst.Name, func(t *testing.T) { t.Run(tst.Name, func(t *testing.T) {
@ -156,8 +247,30 @@ func TestBadLoginFromJSONReader(t *testing.T) {
ServerName: serverName, ServerName: serverName,
}, },
}, },
Derived: &config.Derived{
ApplicationServices: []config.ApplicationService{
{
ID: "anapplicationservice",
ASToken: "astoken",
NamespaceMap: map[string][]config.ApplicationServiceNamespace{
"users": {
{
Exclusive: true,
Regex: "@alice:example.com",
RegexpObject: regexp.MustCompile("@alice:example.com"),
},
},
},
},
},
},
} }
_, cleanup, errRes := LoginFromJSONReader(ctx, strings.NewReader(tst.Body), &userAPI, cfg, nil) req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(tst.Body))
if tst.Token != "" {
req.Header.Add("Authorization", "Bearer "+tst.Token)
}
_, cleanup, errRes := LoginFromJSONReader(req, &userAPI, &userAPI, cfg, nil)
if errRes == nil { if errRes == nil {
cleanup(ctx, nil) cleanup(ctx, nil)
t.Fatalf("LoginFromJSONReader err: got %+v, want code %q", errRes, tst.WantErrCode) t.Fatalf("LoginFromJSONReader err: got %+v, want code %q", errRes, tst.WantErrCode)
@ -170,7 +283,6 @@ func TestBadLoginFromJSONReader(t *testing.T) {
type fakeUserInternalAPI struct { type fakeUserInternalAPI struct {
uapi.ClientUserAPI uapi.ClientUserAPI
UserInternalAPIForLogin
DeletedTokens []string DeletedTokens []string
} }

View file

@ -29,6 +29,7 @@ import (
"github.com/matrix-org/dendrite/clientapi/userutil" "github.com/matrix-org/dendrite/clientapi/userutil"
"github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/dendrite/userapi/api"
uapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib/spec" "github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util" "github.com/matrix-org/util"
) )
@ -44,11 +45,10 @@ const email = "email"
// LoginTypePassword implements https://matrix.org/docs/spec/client_server/r0.6.1#password-based // LoginTypePassword implements https://matrix.org/docs/spec/client_server/r0.6.1#password-based
type LoginTypePassword struct { type LoginTypePassword struct {
UserApi api.ClientUserAPI UserApi uapi.ClientUserAPI
Config *config.ClientAPI Config *config.ClientAPI
Rt *ratelimit.RtFailedLogin Rt *ratelimit.RtFailedLogin
InhibitDevice bool InhibitDevice bool
UserLoginAPI api.UserLoginAPI
} }
func (t *LoginTypePassword) Name() string { func (t *LoginTypePassword) Name() string {
@ -195,7 +195,7 @@ func (t *LoginTypePassword) authenticateDb(ctx context.Context, localpart string
// If we couldn't find the user by the lower cased localpart, try the provided // If we couldn't find the user by the lower cased localpart, try the provided
// localpart as is. // localpart as is.
if !res.Exists { if !res.Exists {
err = t.UserLoginAPI.QueryAccountByPassword(ctx, &api.QueryAccountByPasswordRequest{ err = t.UserApi.QueryAccountByPassword(ctx, &api.QueryAccountByPasswordRequest{
Localpart: localpart, Localpart: localpart,
ServerName: domain, ServerName: domain,
PlaintextPassword: password, PlaintextPassword: password,
@ -339,7 +339,7 @@ func (t *LoginTypePassword) isLdapAdmin(conn *ldap.Conn, username string) (bool,
func (t *LoginTypePassword) getOrCreateAccount(ctx context.Context, localpart string, domain spec.ServerName, admin bool) (*api.Account, *util.JSONResponse) { func (t *LoginTypePassword) getOrCreateAccount(ctx context.Context, localpart string, domain spec.ServerName, admin bool) (*api.Account, *util.JSONResponse) {
var existing api.QueryAccountByLocalpartResponse var existing api.QueryAccountByLocalpartResponse
err := t.UserLoginAPI.QueryAccountByLocalpart(ctx, &api.QueryAccountByLocalpartRequest{ err := t.UserApi.QueryAccountByLocalpart(ctx, &api.QueryAccountByLocalpartRequest{
Localpart: localpart, Localpart: localpart,
ServerName: domain, ServerName: domain,
}, &existing) }, &existing)
@ -359,7 +359,7 @@ func (t *LoginTypePassword) getOrCreateAccount(ctx context.Context, localpart st
accountType = api.AccountTypeAdmin accountType = api.AccountTypeAdmin
} }
var created api.PerformAccountCreationResponse var created api.PerformAccountCreationResponse
err = t.UserLoginAPI.PerformAccountCreation(ctx, &api.PerformAccountCreationRequest{ err = t.UserApi.PerformAccountCreation(ctx, &api.PerformAccountCreationRequest{
AppServiceID: "ldap", AppServiceID: "ldap",
Localpart: localpart, Localpart: localpart,
Password: uuid.New().String(), Password: uuid.New().String(),

View file

@ -55,7 +55,7 @@ type LoginCleanupFunc func(context.Context, *util.JSONResponse)
// https://matrix.org/docs/spec/client_server/r0.6.1#identifier-types // https://matrix.org/docs/spec/client_server/r0.6.1#identifier-types
type LoginIdentifier struct { type LoginIdentifier struct {
Type string `json:"type"` Type string `json:"type"`
// when type = m.id.user // when type = m.id.user or m.id.application_service
User string `json:"user"` User string `json:"user"`
// when type = m.id.thirdparty // when type = m.id.thirdparty
Medium string `json:"medium"` Medium string `json:"medium"`
@ -114,9 +114,8 @@ type UserInteractive struct {
func NewUserInteractive(userAccountAPI api.ClientUserAPI, cfg *config.ClientAPI) *UserInteractive { func NewUserInteractive(userAccountAPI api.ClientUserAPI, cfg *config.ClientAPI) *UserInteractive {
typePassword := &LoginTypePassword{ typePassword := &LoginTypePassword{
UserApi: userAccountAPI, UserApi: userAccountAPI,
UserLoginAPI: userAccountAPI, Config: cfg,
Config: cfg,
} }
return &UserInteractive{ return &UserInteractive{
Flows: []userInteractiveFlow{ Flows: []userInteractiveFlow{

View file

@ -15,6 +15,7 @@ import (
"github.com/matrix-org/dendrite/appservice" "github.com/matrix-org/dendrite/appservice"
"github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/federationapi/statistics"
"github.com/matrix-org/dendrite/internal/caching" "github.com/matrix-org/dendrite/internal/caching"
"github.com/matrix-org/dendrite/internal/httputil" "github.com/matrix-org/dendrite/internal/httputil"
"github.com/matrix-org/dendrite/internal/pushrules" "github.com/matrix-org/dendrite/internal/pushrules"
@ -31,6 +32,7 @@ import (
uapi "github.com/matrix-org/dendrite/userapi/api" uapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/gomatrix" "github.com/matrix-org/gomatrix"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util" "github.com/matrix-org/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
@ -46,6 +48,10 @@ type userDevice struct {
password string password string
} }
var testIsBlacklistedOrBackingOff = func(s spec.ServerName) (*statistics.ServerStatistics, error) {
return &statistics.ServerStatistics{}, nil
}
func TestGetPutDevices(t *testing.T) { func TestGetPutDevices(t *testing.T) {
alice := test.NewUser(t) alice := test.NewUser(t)
bob := test.NewUser(t) bob := test.NewUser(t)
@ -118,7 +124,7 @@ func TestGetPutDevices(t *testing.T) {
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions) cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc. // We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
@ -167,7 +173,7 @@ func TestDeleteDevice(t *testing.T) {
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics) caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
// We mostly need the rsAPI/ for this test, so nil for other APIs/caches etc. // We mostly need the rsAPI/ for this test, so nil for other APIs/caches etc.
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
@ -271,7 +277,7 @@ func TestDeleteDevices(t *testing.T) {
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics) caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
// We mostly need the rsAPI/ for this test, so nil for other APIs/caches etc. // We mostly need the rsAPI/ for this test, so nil for other APIs/caches etc.
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
@ -419,7 +425,7 @@ func TestSetDisplayname(t *testing.T) {
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
asPI := appservice.NewInternalAPI(processCtx, cfg, natsInstance, userAPI, rsAPI) asPI := appservice.NewInternalAPI(processCtx, cfg, natsInstance, userAPI, rsAPI)
AddPublicRoutes(processCtx, routers, cfg, natsInstance, base.CreateFederationClient(cfg, nil), rsAPI, asPI, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, natsInstance, base.CreateFederationClient(cfg, nil), rsAPI, asPI, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
@ -531,7 +537,7 @@ func TestSetAvatarURL(t *testing.T) {
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
asPI := appservice.NewInternalAPI(processCtx, cfg, natsInstance, userAPI, rsAPI) asPI := appservice.NewInternalAPI(processCtx, cfg, natsInstance, userAPI, rsAPI)
AddPublicRoutes(processCtx, routers, cfg, natsInstance, base.CreateFederationClient(cfg, nil), rsAPI, asPI, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, natsInstance, base.CreateFederationClient(cfg, nil), rsAPI, asPI, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
@ -609,7 +615,7 @@ func TestTyping(t *testing.T) {
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
// Needed to create accounts // Needed to create accounts
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
// We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc. // We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc.
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
@ -693,7 +699,7 @@ func TestMembership(t *testing.T) {
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
// Needed to create accounts // Needed to create accounts
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
rsAPI.SetUserAPI(userAPI) rsAPI.SetUserAPI(userAPI)
// We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc. // We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc.
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
@ -932,7 +938,7 @@ func TestCapabilities(t *testing.T) {
// Needed to create accounts // Needed to create accounts
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
// We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc. // We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc.
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
@ -979,7 +985,7 @@ func TestTurnserver(t *testing.T) {
// Needed to create accounts // Needed to create accounts
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
//rsAPI.SetUserAPI(userAPI) //rsAPI.SetUserAPI(userAPI)
// We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc. // We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc.
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
@ -1075,12 +1081,12 @@ func TestTurnserver(t *testing.T) {
// routers := httputil.NewRouters() // routers := httputil.NewRouters()
// cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions) // cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
// // Needed to create accounts // Needed to create accounts
// rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, caching.DisableMetrics) // rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, caching.DisableMetrics)
// rsAPI.SetFederationAPI(nil, nil) // rsAPI.SetFederationAPI(nil, nil)
// userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) // userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
// // We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc. // // We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc.
// AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) // AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
// // Create the users in the userapi and login // // Create the users in the userapi and login
// accessTokens := map[*test.User]userDevice{ // accessTokens := map[*test.User]userDevice{
@ -1254,7 +1260,7 @@ func TestPushRules(t *testing.T) {
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions) cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc. // We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
@ -1641,7 +1647,7 @@ func TestKeys(t *testing.T) {
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions) cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc. // We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
@ -2121,7 +2127,7 @@ func TestKeyBackup(t *testing.T) {
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions) cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc. // We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)

View file

@ -7,6 +7,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/matrix-org/dendrite/federationapi/statistics"
"github.com/matrix-org/dendrite/internal/caching" "github.com/matrix-org/dendrite/internal/caching"
"github.com/matrix-org/dendrite/internal/sqlutil" "github.com/matrix-org/dendrite/internal/sqlutil"
"github.com/matrix-org/dendrite/setup/jetstream" "github.com/matrix-org/dendrite/setup/jetstream"
@ -21,6 +22,10 @@ import (
uapi "github.com/matrix-org/dendrite/userapi/api" uapi "github.com/matrix-org/dendrite/userapi/api"
) )
var testIsBlacklistedOrBackingOff = func(s spec.ServerName) (*statistics.ServerStatistics, error) {
return &statistics.ServerStatistics{}, nil
}
func TestJoinRoomByIDOrAlias(t *testing.T) { func TestJoinRoomByIDOrAlias(t *testing.T) {
alice := test.NewUser(t) alice := test.NewUser(t)
bob := test.NewUser(t) bob := test.NewUser(t)
@ -36,7 +41,7 @@ func TestJoinRoomByIDOrAlias(t *testing.T) {
natsInstance := jetstream.NATSInstance{} natsInstance := jetstream.NATSInstance{}
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) // creates the rs.Inputer etc rsAPI.SetFederationAPI(nil, nil) // creates the rs.Inputer etc
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI) asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI)
// Create the users in the userapi // Create the users in the userapi

View file

@ -65,9 +65,8 @@ func UploadCrossSigningDeviceKeys(
} }
} }
typePassword := auth.LoginTypePassword{ typePassword := auth.LoginTypePassword{
UserApi: accountAPI, UserApi: accountAPI,
UserLoginAPI: accountAPI, Config: cfg,
Config: cfg,
} }
if _, authErr := typePassword.Login(req.Context(), &uploadReq.Auth.PasswordRequest); authErr != nil { if _, authErr := typePassword.Login(req.Context(), &uploadReq.Auth.PasswordRequest); authErr != nil {
return *authErr return *authErr

View file

@ -19,6 +19,7 @@ import (
"net/http" "net/http"
"github.com/matrix-org/dendrite/clientapi/auth" "github.com/matrix-org/dendrite/clientapi/auth"
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/clientapi/ratelimit" "github.com/matrix-org/dendrite/clientapi/ratelimit"
"github.com/matrix-org/dendrite/clientapi/userutil" "github.com/matrix-org/dendrite/clientapi/userutil"
"github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/setup/config"
@ -42,15 +43,6 @@ type flow struct {
Type string `json:"type"` Type string `json:"type"`
} }
func passwordLogin() flows {
f := flows{}
s := flow{
Type: "m.login.password",
}
f.Flows = append(f.Flows, s)
return f
}
// Login implements GET and POST /login // Login implements GET and POST /login
func Login( func Login(
req *http.Request, userAPI userapi.ClientUserAPI, req *http.Request, userAPI userapi.ClientUserAPI,
@ -58,13 +50,19 @@ func Login(
rt *ratelimit.RtFailedLogin, rt *ratelimit.RtFailedLogin,
) util.JSONResponse { ) util.JSONResponse {
if req.Method == http.MethodGet { if req.Method == http.MethodGet {
// TODO: support other forms of login other than password, depending on config options loginFlows := []flow{{Type: authtypes.LoginTypePassword}}
if len(cfg.Derived.ApplicationServices) > 0 {
loginFlows = append(loginFlows, flow{Type: authtypes.LoginTypeApplicationService})
}
// TODO: support other forms of login, depending on config options
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusOK, Code: http.StatusOK,
JSON: passwordLogin(), JSON: flows{
Flows: loginFlows,
},
} }
} else if req.Method == http.MethodPost { } else if req.Method == http.MethodPost {
login, cleanup, authErr := auth.LoginFromJSONReader(req.Context(), req.Body, userAPI, cfg, rt) login, cleanup, authErr := auth.LoginFromJSONReader(req, userAPI, userAPI, cfg, rt)
if authErr != nil { if authErr != nil {
return *authErr return *authErr
} }

View file

@ -49,7 +49,7 @@ func TestLogin(t *testing.T) {
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
// Needed for /login // Needed for /login
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
// We mostly need the userAPI for this test, so nil for other APIs/caches etc. // We mostly need the userAPI for this test, so nil for other APIs/caches etc.
Setup(routers, cfg, nil, nil, userAPI, nil, nil, nil, nil, nil, nil, nil, caching.DisableMetrics) Setup(routers, cfg, nil, nil, userAPI, nil, nil, nil, nil, nil, nil, nil, caching.DisableMetrics)
@ -114,6 +114,44 @@ func TestLogin(t *testing.T) {
ctx := context.Background() ctx := context.Background()
// Inject a dummy application service, so we have a "m.login.application_service"
// in the login flows
as := &config.ApplicationService{}
cfg.AppServiceAPI.Derived.ApplicationServices = []config.ApplicationService{*as}
t.Run("Supported log-in flows are returned", func(t *testing.T) {
req := test.NewRequest(t, http.MethodGet, "/_matrix/client/v3/login")
rec := httptest.NewRecorder()
routers.Client.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("failed to get log-in flows: %s", rec.Body.String())
}
t.Logf("response: %s", rec.Body.String())
resp := flows{}
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatal(err)
}
appServiceFound := false
passwordFound := false
for _, flow := range resp.Flows {
if flow.Type == "m.login.password" {
passwordFound = true
} else if flow.Type == "m.login.application_service" {
appServiceFound = true
} else {
t.Fatalf("got unknown login flow: %s", flow.Type)
}
}
if !appServiceFound {
t.Fatal("m.login.application_service missing from login flows")
}
if !passwordFound {
t.Fatal("m.login.password missing from login flows")
}
})
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
req := test.NewRequest(t, http.MethodPost, "/_matrix/client/v3/login", test.WithJSONBody(t, map[string]interface{}{ req := test.NewRequest(t, http.MethodPost, "/_matrix/client/v3/login", test.WithJSONBody(t, map[string]interface{}{

View file

@ -181,18 +181,6 @@ func SendKick(
return *errRes return *errRes
} }
pl, errRes := getPowerlevels(req, rsAPI, roomID)
if errRes != nil {
return *errRes
}
allowedToKick := pl.UserLevel(*senderID) >= pl.Kick
if !allowedToKick {
return util.JSONResponse{
Code: http.StatusForbidden,
JSON: spec.Forbidden("You don't have permission to kick this user, power level too low."),
}
}
bodyUserID, err := spec.NewUserID(body.UserID, true) bodyUserID, err := spec.NewUserID(body.UserID, true)
if err != nil { if err != nil {
return util.JSONResponse{ return util.JSONResponse{
@ -200,6 +188,19 @@ func SendKick(
JSON: spec.BadJSON("body userID is invalid"), JSON: spec.BadJSON("body userID is invalid"),
} }
} }
pl, errRes := getPowerlevels(req, rsAPI, roomID)
if errRes != nil {
return *errRes
}
allowedToKick := pl.UserLevel(*senderID) >= pl.Kick || bodyUserID.String() == deviceUserID.String()
if !allowedToKick {
return util.JSONResponse{
Code: http.StatusForbidden,
JSON: spec.Forbidden("You don't have permission to kick this user, power level too low."),
}
}
var queryRes roomserverAPI.QueryMembershipForUserResponse var queryRes roomserverAPI.QueryMembershipForUserResponse
err = rsAPI.QueryMembershipForUser(req.Context(), &roomserverAPI.QueryMembershipForUserRequest{ err = rsAPI.QueryMembershipForUser(req.Context(), &roomserverAPI.QueryMembershipForUserRequest{
RoomID: roomID, RoomID: roomID,

View file

@ -23,13 +23,12 @@ import (
"github.com/matrix-org/dendrite/clientapi/producers" "github.com/matrix-org/dendrite/clientapi/producers"
"github.com/matrix-org/gomatrixserverlib/spec" "github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/dendrite/userapi/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, userAPI api.ClientUserAPI, syncProducer *producers.SyncAPIProducer, device *userapi.Device, roomID, receiptType, eventID string) util.JSONResponse { func SetReceipt(req *http.Request, userAPI userapi.ClientUserAPI, syncProducer *producers.SyncAPIProducer, device *userapi.Device, roomID, receiptType, eventID string) util.JSONResponse {
timestamp := spec.AsTimestamp(time.Now()) timestamp := spec.AsTimestamp(time.Now())
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"roomID": roomID, "roomID": roomID,
@ -54,13 +53,13 @@ func SetReceipt(req *http.Request, userAPI api.ClientUserAPI, syncProducer *prod
} }
} }
dataReq := api.InputAccountDataRequest{ dataReq := userapi.InputAccountDataRequest{
UserID: device.UserID, UserID: device.UserID,
DataType: "m.fully_read", DataType: "m.fully_read",
RoomID: roomID, RoomID: roomID,
AccountData: data, AccountData: data,
} }
dataRes := api.InputAccountDataResponse{} dataRes := userapi.InputAccountDataResponse{}
if err := userAPI.InputAccountData(req.Context(), &dataReq, &dataRes); err != nil { if err := userAPI.InputAccountData(req.Context(), &dataReq, &dataRes); err != nil {
util.GetLogger(req.Context()).WithError(err).Error("userAPI.InputAccountData failed") util.GetLogger(req.Context()).WithError(err).Error("userAPI.InputAccountData failed")
return util.ErrorResponse(err) return util.ErrorResponse(err)

View file

@ -649,6 +649,16 @@ func handleGuestRegistration(
} }
} }
// localpartMatchesExclusiveNamespaces will check if a given username matches any
// application service's exclusive users namespace
func localpartMatchesExclusiveNamespaces(
cfg *config.ClientAPI,
localpart string,
) bool {
userID := userutil.MakeUserID(localpart, cfg.Matrix.ServerName)
return cfg.Derived.ExclusiveApplicationServicesUsernameRegexp.MatchString(userID)
}
// handleRegistrationFlow will direct and complete registration flow stages // handleRegistrationFlow will direct and complete registration flow stages
// that the client has requested. // that the client has requested.
// nolint: gocyclo // nolint: gocyclo
@ -697,7 +707,7 @@ func handleRegistrationFlow(
// If an access token is provided, ignore this check this is an appservice // If an access token is provided, ignore this check this is an appservice
// request and we will validate in validateApplicationService // request and we will validate in validateApplicationService
if len(cfg.Derived.ApplicationServices) != 0 && if len(cfg.Derived.ApplicationServices) != 0 &&
UsernameMatchesExclusiveNamespaces(cfg, r.Username) { localpartMatchesExclusiveNamespaces(cfg, r.Username) {
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
JSON: spec.ASExclusive("This username is reserved by an application service."), JSON: spec.ASExclusive("This username is reserved by an application service."),
@ -801,7 +811,7 @@ func handleApplicationServiceRegistration(
// Check application service register user request is valid. // Check application service register user request is valid.
// The application service's ID is returned if so. // The application service's ID is returned if so.
appserviceID, err := validateApplicationService( appserviceID, err := internal.ValidateApplicationServiceRequest(
cfg, r.Username, accessToken, cfg, r.Username, accessToken,
) )
if err != nil { if err != nil {

View file

@ -298,25 +298,29 @@ func Test_register(t *testing.T) {
guestsDisabled bool guestsDisabled bool
enableRecaptcha bool enableRecaptcha bool
captchaBody string captchaBody string
wantResponse util.JSONResponse // in case of an error, the expected response
wantErrorResponse util.JSONResponse
// in case of success, the expected username assigned
wantUsername string
}{ }{
{ {
name: "disallow guests", name: "disallow guests",
kind: "guest", kind: "guest",
guestsDisabled: true, guestsDisabled: true,
wantResponse: util.JSONResponse{ wantErrorResponse: util.JSONResponse{
Code: http.StatusForbidden, Code: http.StatusForbidden,
JSON: spec.Forbidden(`Guest registration is disabled on "test"`), JSON: spec.Forbidden(`Guest registration is disabled on "test"`),
}, },
}, },
{ {
name: "allow guests", name: "allow guests",
kind: "guest", kind: "guest",
wantUsername: "1",
}, },
{ {
name: "unknown login type", name: "unknown login type",
loginType: "im.not.known", loginType: "im.not.known",
wantResponse: util.JSONResponse{ wantErrorResponse: util.JSONResponse{
Code: http.StatusNotImplemented, Code: http.StatusNotImplemented,
JSON: spec.Unknown("unknown/unimplemented auth type"), JSON: spec.Unknown("unknown/unimplemented auth type"),
}, },
@ -324,25 +328,33 @@ func Test_register(t *testing.T) {
{ {
name: "disabled registration", name: "disabled registration",
registrationDisabled: true, registrationDisabled: true,
wantResponse: util.JSONResponse{ wantErrorResponse: util.JSONResponse{
Code: http.StatusForbidden, Code: http.StatusForbidden,
JSON: spec.Forbidden(`Registration is disabled on "test"`), JSON: spec.Forbidden(`Registration is disabled on "test"`),
}, },
}, },
{ {
name: "successful registration, numeric ID", name: "successful registration, numeric ID",
username: "", username: "",
password: "someRandomPassword", password: "someRandomPassword",
forceEmpty: true, forceEmpty: true,
wantUsername: "2",
}, },
{ {
name: "successful registration", name: "successful registration",
username: "success", username: "success",
}, },
{
name: "successful registration, sequential numeric ID",
username: "",
password: "someRandomPassword",
forceEmpty: true,
wantUsername: "3",
},
{ {
name: "failing registration - user already exists", name: "failing registration - user already exists",
username: "success", username: "success",
wantResponse: util.JSONResponse{ wantErrorResponse: util.JSONResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
JSON: spec.UserInUse("Desired user ID is already taken."), JSON: spec.UserInUse("Desired user ID is already taken."),
}, },
@ -352,14 +364,14 @@ func Test_register(t *testing.T) {
username: "LOWERCASED", // this is going to be lower-cased username: "LOWERCASED", // this is going to be lower-cased
}, },
{ {
name: "invalid username", name: "invalid username",
username: "#totalyNotValid", username: "#totalyNotValid",
wantResponse: *internal.UsernameResponse(internal.ErrUsernameInvalid), wantErrorResponse: *internal.UsernameResponse(internal.ErrUsernameInvalid),
}, },
{ {
name: "numeric username is forbidden", name: "numeric username is forbidden",
username: "1337", username: "1337",
wantResponse: util.JSONResponse{ wantErrorResponse: util.JSONResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
JSON: spec.InvalidUsername("Numeric user IDs are reserved"), JSON: spec.InvalidUsername("Numeric user IDs are reserved"),
}, },
@ -367,7 +379,7 @@ func Test_register(t *testing.T) {
{ {
name: "disabled recaptcha login", name: "disabled recaptcha login",
loginType: authtypes.LoginTypeRecaptcha, loginType: authtypes.LoginTypeRecaptcha,
wantResponse: util.JSONResponse{ wantErrorResponse: util.JSONResponse{
Code: http.StatusForbidden, Code: http.StatusForbidden,
JSON: spec.Unknown(ErrCaptchaDisabled.Error()), JSON: spec.Unknown(ErrCaptchaDisabled.Error()),
}, },
@ -376,7 +388,7 @@ func Test_register(t *testing.T) {
name: "enabled recaptcha, no response defined", name: "enabled recaptcha, no response defined",
enableRecaptcha: true, enableRecaptcha: true,
loginType: authtypes.LoginTypeRecaptcha, loginType: authtypes.LoginTypeRecaptcha,
wantResponse: util.JSONResponse{ wantErrorResponse: util.JSONResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
JSON: spec.BadJSON(ErrMissingResponse.Error()), JSON: spec.BadJSON(ErrMissingResponse.Error()),
}, },
@ -386,7 +398,7 @@ func Test_register(t *testing.T) {
enableRecaptcha: true, enableRecaptcha: true,
loginType: authtypes.LoginTypeRecaptcha, loginType: authtypes.LoginTypeRecaptcha,
captchaBody: `notvalid`, captchaBody: `notvalid`,
wantResponse: util.JSONResponse{ wantErrorResponse: util.JSONResponse{
Code: http.StatusUnauthorized, Code: http.StatusUnauthorized,
JSON: spec.BadJSON(ErrInvalidCaptcha.Error()), JSON: spec.BadJSON(ErrInvalidCaptcha.Error()),
}, },
@ -398,11 +410,11 @@ func Test_register(t *testing.T) {
captchaBody: `success`, captchaBody: `success`,
}, },
{ {
name: "captcha invalid from remote", name: "captcha invalid from remote",
enableRecaptcha: true, enableRecaptcha: true,
loginType: authtypes.LoginTypeRecaptcha, loginType: authtypes.LoginTypeRecaptcha,
captchaBody: `i should fail for other reasons`, captchaBody: `i should fail for other reasons`,
wantResponse: util.JSONResponse{Code: http.StatusInternalServerError, JSON: spec.InternalServerError{}}, wantErrorResponse: util.JSONResponse{Code: http.StatusInternalServerError, JSON: spec.InternalServerError{}},
}, },
} }
@ -416,7 +428,7 @@ func Test_register(t *testing.T) {
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions) cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
@ -486,8 +498,8 @@ func Test_register(t *testing.T) {
t.Fatalf("unexpected registration flows: %+v, want %+v", r.Flows, cfg.Derived.Registration.Flows) t.Fatalf("unexpected registration flows: %+v, want %+v", r.Flows, cfg.Derived.Registration.Flows)
} }
case spec.MatrixError: case spec.MatrixError:
if !reflect.DeepEqual(tc.wantResponse, resp) { if !reflect.DeepEqual(tc.wantErrorResponse, resp) {
t.Fatalf("(%s), unexpected response: %+v, want: %+v", tc.name, resp, tc.wantResponse) t.Fatalf("(%s), unexpected response: %+v, want: %+v", tc.name, resp, tc.wantErrorResponse)
} }
return return
case registerResponse: case registerResponse:
@ -505,6 +517,13 @@ func Test_register(t *testing.T) {
if r.DeviceID == "" { if r.DeviceID == "" {
t.Fatalf("missing deviceID in response") t.Fatalf("missing deviceID in response")
} }
// if an expected username is provided, assert that it is a match
if tc.wantUsername != "" {
wantUserID := strings.ToLower(fmt.Sprintf("@%s:%s", tc.wantUsername, "test"))
if wantUserID != r.UserID {
t.Fatalf("unexpected userID: %s, want %s", r.UserID, wantUserID)
}
}
return return
default: default:
t.Logf("Got response: %T", resp.JSON) t.Logf("Got response: %T", resp.JSON)
@ -541,44 +560,29 @@ func Test_register(t *testing.T) {
resp = Register(req, userAPI, &cfg.ClientAPI) resp = Register(req, userAPI, &cfg.ClientAPI)
switch resp.JSON.(type) { switch rr := resp.JSON.(type) {
case spec.InternalServerError: case spec.InternalServerError, spec.MatrixError, util.JSONResponse:
if !reflect.DeepEqual(tc.wantResponse, resp) { if !reflect.DeepEqual(tc.wantErrorResponse, resp) {
t.Fatalf("unexpected response: %+v, want: %+v", resp, tc.wantResponse) t.Fatalf("unexpected response: %+v, want: %+v", resp, tc.wantErrorResponse)
} }
return return
case spec.MatrixError: case registerResponse:
if !reflect.DeepEqual(tc.wantResponse, resp) { // validate the response
t.Fatalf("unexpected response: %+v, want: %+v", resp, tc.wantResponse) if tc.wantUsername != "" {
// if an expected username is provided, assert that it is a match
wantUserID := strings.ToLower(fmt.Sprintf("@%s:%s", tc.wantUsername, "test"))
if wantUserID != rr.UserID {
t.Fatalf("unexpected userID: %s, want %s", rr.UserID, wantUserID)
}
} }
return if rr.DeviceID != *reg.DeviceID {
case util.JSONResponse: t.Fatalf("unexpected deviceID: %s, want %s", rr.DeviceID, *reg.DeviceID)
if !reflect.DeepEqual(tc.wantResponse, resp) {
t.Fatalf("unexpected response: %+v, want: %+v", resp, tc.wantResponse)
} }
return if rr.AccessToken == "" {
} t.Fatalf("missing accessToken in response")
}
rr, ok := resp.JSON.(registerResponse) default:
if !ok { t.Fatalf("expected one of internalservererror, matrixerror, jsonresponse, registerresponse, got %T", resp.JSON)
t.Fatalf("expected a registerresponse, got %T", resp.JSON)
}
// validate the response
if tc.forceEmpty {
// when not supplying a username, one will be generated. Given this _SHOULD_ be
// the second user, set the username accordingly
reg.Username = "2"
}
wantUserID := strings.ToLower(fmt.Sprintf("@%s:%s", reg.Username, "test"))
if wantUserID != rr.UserID {
t.Fatalf("unexpected userID: %s, want %s", rr.UserID, wantUserID)
}
if rr.DeviceID != *reg.DeviceID {
t.Fatalf("unexpected deviceID: %s, want %s", rr.DeviceID, *reg.DeviceID)
}
if rr.AccessToken == "" {
t.Fatalf("missing accessToken in response")
} }
}) })
} }
@ -596,7 +600,7 @@ func TestRegisterUserWithDisplayName(t *testing.T) {
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions) cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
deviceName, deviceID := "deviceName", "deviceID" deviceName, deviceID := "deviceName", "deviceID"
expectedDisplayName := "DisplayName" expectedDisplayName := "DisplayName"
response := completeRegistration( response := completeRegistration(
@ -638,7 +642,7 @@ func TestRegisterAdminUsingSharedSecret(t *testing.T) {
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics) caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
expectedDisplayName := "rabbit" expectedDisplayName := "rabbit"
jsonStr := []byte(`{"admin":true,"mac":"24dca3bba410e43fe64b9b5c28306693bf3baa9f","nonce":"759f047f312b99ff428b21d581256f8592b8976e58bc1b543972dc6147e529a79657605b52d7becd160ff5137f3de11975684319187e06901955f79e5a6c5a79","password":"wonderland","username":"alice","displayname":"rabbit"}`) jsonStr := []byte(`{"admin":true,"mac":"24dca3bba410e43fe64b9b5c28306693bf3baa9f","nonce":"759f047f312b99ff428b21d581256f8592b8976e58bc1b543972dc6147e529a79657605b52d7becd160ff5137f3de11975684319187e06901955f79e5a6c5a79","password":"wonderland","username":"alice","displayname":"rabbit"}`)

View file

@ -224,7 +224,7 @@ func SendEvent(
req.Context(), rsAPI, req.Context(), rsAPI,
api.KindNew, api.KindNew,
[]*types.HeaderedEvent{ []*types.HeaderedEvent{
&types.HeaderedEvent{PDU: e}, {PDU: e},
}, },
device.UserDomain(), device.UserDomain(),
domain, domain,

View file

@ -213,14 +213,15 @@ func main() {
natsInstance := jetstream.NATSInstance{} natsInstance := jetstream.NATSInstance{}
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.EnableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.EnableMetrics)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federation)
asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI)
rsAPI.SetAppserviceAPI(asAPI)
fsAPI := federationapi.NewInternalAPI( fsAPI := federationapi.NewInternalAPI(
processCtx, cfg, cm, &natsInstance, federation, rsAPI, caches, keyRing, true, processCtx, cfg, cm, &natsInstance, federation, rsAPI, caches, keyRing, true,
) )
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federation, caching.EnableMetrics, fsAPI.IsBlacklistedOrBackingOff)
asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI)
rsAPI.SetAppserviceAPI(asAPI)
rsAPI.SetFederationAPI(fsAPI, keyRing) rsAPI.SetFederationAPI(fsAPI, keyRing)
monolith := setup.Monolith{ monolith := setup.Monolith{

View file

@ -162,7 +162,7 @@ func main() {
// dependency. Other components also need updating after their dependencies are up. // dependency. Other components also need updating after their dependencies are up.
rsAPI.SetFederationAPI(fsAPI, keyRing) rsAPI.SetFederationAPI(fsAPI, keyRing)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federationClient) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federationClient, caching.EnableMetrics, fsAPI.IsBlacklistedOrBackingOff)
asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI) asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI)
rsAPI.SetAppserviceAPI(asAPI) rsAPI.SetAppserviceAPI(asAPI)

View file

@ -325,6 +325,10 @@ user_api:
auto_join_rooms: auto_join_rooms:
# - "#main:matrix.org" # - "#main:matrix.org"
# The number of workers to start for the DeviceListUpdater. Defaults to 8.
# This only needs updating if the "InputDeviceListUpdate" stream keeps growing indefinitely.
# worker_count: 8
# Configuration for Opentracing. # Configuration for Opentracing.
# See https://github.com/matrix-org/dendrite/tree/master/docs/tracing for information on # See https://github.com/matrix-org/dendrite/tree/master/docs/tracing for information on
# how this works and how to set it up. # how this works and how to set it up.

View file

@ -24,7 +24,7 @@ No, although a good portion of the Matrix specification has been implemented. Mo
Dendrite development is currently supported by a small team of developers and due to those limited resources, the majority of the effort is focused on getting Dendrite to be Dendrite development is currently supported by a small team of developers and due to those limited resources, the majority of the effort is focused on getting Dendrite to be
specification complete. If there are major features you're requesting (e.g. new administration endpoints), we'd like to strongly encourage you to join the community in supporting specification complete. If there are major features you're requesting (e.g. new administration endpoints), we'd like to strongly encourage you to join the community in supporting
the development efforts through [contributing](../development/contributing). the development efforts through [contributing](./development/CONTRIBUTING.md).
## Is there a migration path from Synapse to Dendrite? ## Is there a migration path from Synapse to Dendrite?
@ -105,7 +105,7 @@ This can be done by performing a room upgrade. Use the command `/upgraderoom <ve
## How do I reset somebody's password on my server? ## How do I reset somebody's password on my server?
Use the admin endpoint [resetpassword](./administration/adminapi#post-_dendriteadminresetpassworduserid) Use the admin endpoint [resetpassword](./administration/4_adminapi.md#post-_dendriteadminresetpassworduserid)
## Should I use PostgreSQL or SQLite for my databases? ## Should I use PostgreSQL or SQLite for my databases?

View file

@ -26,6 +26,8 @@ docker run --rm --entrypoint="/usr/bin/generate-keys" \
-v $(pwd)/config:/mnt \ -v $(pwd)/config:/mnt \
matrixdotorg/dendrite-monolith:latest \ matrixdotorg/dendrite-monolith:latest \
-private-key /mnt/matrix_key.pem -private-key /mnt/matrix_key.pem
# Windows equivalent: docker run --rm --entrypoint="/usr/bin/generate-keys" -v %cd%/config:/mnt matrixdotorg/dendrite-monolith:latest -private-key /mnt/matrix_key.pem
``` ```
(**NOTE**: This only needs to be executed **once**, as you otherwise overwrite the key) (**NOTE**: This only needs to be executed **once**, as you otherwise overwrite the key)
@ -44,6 +46,8 @@ docker run --rm --entrypoint="/bin/sh" \
-dir /var/dendrite/ \ -dir /var/dendrite/ \
-db postgres://dendrite:itsasecret@postgres/dendrite?sslmode=disable \ -db postgres://dendrite:itsasecret@postgres/dendrite?sslmode=disable \
-server YourDomainHere > /mnt/dendrite.yaml" -server YourDomainHere > /mnt/dendrite.yaml"
# Windows equivalent: docker run --rm --entrypoint="/bin/sh" -v %cd%/config:/mnt matrixdotorg/dendrite-monolith:latest -c "/usr/bin/generate-config -dir /var/dendrite/ -db postgres://dendrite:itsasecret@postgres/dendrite?sslmode=disable -server YourDomainHere > /mnt/dendrite.yaml"
``` ```
You can then change `config/dendrite.yaml` to your liking. You can then change `config/dendrite.yaml` to your liking.

View file

@ -24,7 +24,6 @@ import (
"github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib/fclient"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"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"
@ -102,7 +101,7 @@ func NewInternalAPI(
caches *caching.Caches, caches *caching.Caches,
keyRing *gomatrixserverlib.KeyRing, keyRing *gomatrixserverlib.KeyRing,
resetBlacklist bool, resetBlacklist bool,
) api.FederationInternalAPI { ) *internal.FederationInternalAPI {
cfg := &dendriteCfg.FederationAPI cfg := &dendriteCfg.FederationAPI
federationDB, err := storage.NewDatabase(processContext.Context(), cm, &cfg.Database, caches, dendriteCfg.Global.IsLocalServerName) federationDB, err := storage.NewDatabase(processContext.Context(), cm, &cfg.Database, caches, dendriteCfg.Global.IsLocalServerName)
@ -126,7 +125,7 @@ func NewInternalAPI(
queues := queue.NewOutgoingQueues( queues := queue.NewOutgoingQueues(
federationDB, processContext, federationDB, processContext,
cfg.Matrix.DisableFederation, cfg.Matrix.DisableFederation,
cfg.Matrix.ServerName, federation, rsAPI, &stats, cfg.Matrix.ServerName, federation, &stats,
signingInfo, signingInfo,
) )

View file

@ -112,7 +112,7 @@ func NewFederationInternalAPI(
} }
} }
func (a *FederationInternalAPI) isBlacklistedOrBackingOff(s spec.ServerName) (*statistics.ServerStatistics, error) { func (a *FederationInternalAPI) IsBlacklistedOrBackingOff(s spec.ServerName) (*statistics.ServerStatistics, error) {
stats := a.statistics.ForServer(s) stats := a.statistics.ForServer(s)
if stats.Blacklisted() { if stats.Blacklisted() {
return stats, &api.FederationClientError{ return stats, &api.FederationClientError{
@ -151,7 +151,7 @@ func failBlacklistableError(err error, stats *statistics.ServerStatistics) (unti
func (a *FederationInternalAPI) doRequestIfNotBackingOffOrBlacklisted( func (a *FederationInternalAPI) doRequestIfNotBackingOffOrBlacklisted(
s spec.ServerName, request func() (interface{}, error), s spec.ServerName, request func() (interface{}, error),
) (interface{}, error) { ) (interface{}, error) {
stats, err := a.isBlacklistedOrBackingOff(s) stats, err := a.IsBlacklistedOrBackingOff(s)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -65,7 +65,7 @@ func TestFederationClientQueryKeys(t *testing.T) {
queues := queue.NewOutgoingQueues( queues := queue.NewOutgoingQueues(
testDB, process.NewProcessContext(), testDB, process.NewProcessContext(),
false, false,
cfg.Matrix.ServerName, fedClient, nil, &stats, cfg.Matrix.ServerName, fedClient, &stats,
nil, nil,
) )
fedapi := FederationInternalAPI{ fedapi := FederationInternalAPI{
@ -96,7 +96,7 @@ func TestFederationClientQueryKeysBlacklisted(t *testing.T) {
queues := queue.NewOutgoingQueues( queues := queue.NewOutgoingQueues(
testDB, process.NewProcessContext(), testDB, process.NewProcessContext(),
false, false,
cfg.Matrix.ServerName, fedClient, nil, &stats, cfg.Matrix.ServerName, fedClient, &stats,
nil, nil,
) )
fedapi := FederationInternalAPI{ fedapi := FederationInternalAPI{
@ -126,7 +126,7 @@ func TestFederationClientQueryKeysFailure(t *testing.T) {
queues := queue.NewOutgoingQueues( queues := queue.NewOutgoingQueues(
testDB, process.NewProcessContext(), testDB, process.NewProcessContext(),
false, false,
cfg.Matrix.ServerName, fedClient, nil, &stats, cfg.Matrix.ServerName, fedClient, &stats,
nil, nil,
) )
fedapi := FederationInternalAPI{ fedapi := FederationInternalAPI{
@ -156,7 +156,7 @@ func TestFederationClientClaimKeys(t *testing.T) {
queues := queue.NewOutgoingQueues( queues := queue.NewOutgoingQueues(
testDB, process.NewProcessContext(), testDB, process.NewProcessContext(),
false, false,
cfg.Matrix.ServerName, fedClient, nil, &stats, cfg.Matrix.ServerName, fedClient, &stats,
nil, nil,
) )
fedapi := FederationInternalAPI{ fedapi := FederationInternalAPI{
@ -187,7 +187,7 @@ func TestFederationClientClaimKeysBlacklisted(t *testing.T) {
queues := queue.NewOutgoingQueues( queues := queue.NewOutgoingQueues(
testDB, process.NewProcessContext(), testDB, process.NewProcessContext(),
false, false,
cfg.Matrix.ServerName, fedClient, nil, &stats, cfg.Matrix.ServerName, fedClient, &stats,
nil, nil,
) )
fedapi := FederationInternalAPI{ fedapi := FederationInternalAPI{

View file

@ -70,7 +70,7 @@ func TestPerformWakeupServers(t *testing.T) {
queues := queue.NewOutgoingQueues( queues := queue.NewOutgoingQueues(
testDB, process.NewProcessContext(), testDB, process.NewProcessContext(),
false, false,
cfg.Matrix.ServerName, fedClient, nil, &stats, cfg.Matrix.ServerName, fedClient, &stats,
nil, nil,
) )
fedAPI := NewFederationInternalAPI( fedAPI := NewFederationInternalAPI(
@ -116,7 +116,7 @@ func TestQueryRelayServers(t *testing.T) {
queues := queue.NewOutgoingQueues( queues := queue.NewOutgoingQueues(
testDB, process.NewProcessContext(), testDB, process.NewProcessContext(),
false, false,
cfg.Matrix.ServerName, fedClient, nil, &stats, cfg.Matrix.ServerName, fedClient, &stats,
nil, nil,
) )
fedAPI := NewFederationInternalAPI( fedAPI := NewFederationInternalAPI(
@ -157,7 +157,7 @@ func TestRemoveRelayServers(t *testing.T) {
queues := queue.NewOutgoingQueues( queues := queue.NewOutgoingQueues(
testDB, process.NewProcessContext(), testDB, process.NewProcessContext(),
false, false,
cfg.Matrix.ServerName, fedClient, nil, &stats, cfg.Matrix.ServerName, fedClient, &stats,
nil, nil,
) )
fedAPI := NewFederationInternalAPI( fedAPI := NewFederationInternalAPI(
@ -197,7 +197,7 @@ func TestPerformDirectoryLookup(t *testing.T) {
queues := queue.NewOutgoingQueues( queues := queue.NewOutgoingQueues(
testDB, process.NewProcessContext(), testDB, process.NewProcessContext(),
false, false,
cfg.Matrix.ServerName, fedClient, nil, &stats, cfg.Matrix.ServerName, fedClient, &stats,
nil, nil,
) )
fedAPI := NewFederationInternalAPI( fedAPI := NewFederationInternalAPI(
@ -236,7 +236,7 @@ func TestPerformDirectoryLookupRelaying(t *testing.T) {
queues := queue.NewOutgoingQueues( queues := queue.NewOutgoingQueues(
testDB, process.NewProcessContext(), testDB, process.NewProcessContext(),
false, false,
cfg.Matrix.ServerName, fedClient, nil, &stats, cfg.Matrix.ServerName, fedClient, &stats,
nil, nil,
) )
fedAPI := NewFederationInternalAPI( fedAPI := NewFederationInternalAPI(

View file

@ -31,7 +31,6 @@ import (
"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"
"github.com/matrix-org/dendrite/federationapi/storage/shared/receipt" "github.com/matrix-org/dendrite/federationapi/storage/shared/receipt"
"github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/roomserver/types" "github.com/matrix-org/dendrite/roomserver/types"
"github.com/matrix-org/dendrite/setup/process" "github.com/matrix-org/dendrite/setup/process"
) )
@ -53,7 +52,6 @@ type destinationQueue struct {
db storage.Database db storage.Database
process *process.ProcessContext process *process.ProcessContext
signing map[spec.ServerName]*fclient.SigningIdentity signing map[spec.ServerName]*fclient.SigningIdentity
rsAPI api.FederationRoomserverAPI
client fclient.FederationClient // federation client client fclient.FederationClient // federation client
origin spec.ServerName // origin of requests origin spec.ServerName // origin of requests
destination spec.ServerName // destination of requests destination spec.ServerName // destination of requests

View file

@ -27,12 +27,10 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"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"
"github.com/matrix-org/dendrite/federationapi/storage/shared/receipt" "github.com/matrix-org/dendrite/federationapi/storage/shared/receipt"
"github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/roomserver/types" "github.com/matrix-org/dendrite/roomserver/types"
"github.com/matrix-org/dendrite/setup/process" "github.com/matrix-org/dendrite/setup/process"
) )
@ -43,7 +41,6 @@ type OutgoingQueues struct {
db storage.Database db storage.Database
process *process.ProcessContext process *process.ProcessContext
disabled bool disabled bool
rsAPI api.FederationRoomserverAPI
origin spec.ServerName origin spec.ServerName
client fclient.FederationClient client fclient.FederationClient
statistics *statistics.Statistics statistics *statistics.Statistics
@ -90,7 +87,6 @@ func NewOutgoingQueues(
disabled bool, disabled bool,
origin spec.ServerName, origin spec.ServerName,
client fclient.FederationClient, client fclient.FederationClient,
rsAPI api.FederationRoomserverAPI,
statistics *statistics.Statistics, statistics *statistics.Statistics,
signing []*fclient.SigningIdentity, signing []*fclient.SigningIdentity,
) *OutgoingQueues { ) *OutgoingQueues {
@ -98,7 +94,6 @@ func NewOutgoingQueues(
disabled: disabled, disabled: disabled,
process: process, process: process,
db: db, db: db,
rsAPI: rsAPI,
origin: origin, origin: origin,
client: client, client: client,
statistics: statistics, statistics: statistics,
@ -162,7 +157,6 @@ func (oqs *OutgoingQueues) getQueue(destination spec.ServerName) *destinationQue
queues: oqs, queues: oqs,
db: oqs.db, db: oqs.db,
process: oqs.process, process: oqs.process,
rsAPI: oqs.rsAPI,
origin: oqs.origin, origin: oqs.origin,
destination: destination, destination: destination,
client: oqs.client, client: oqs.client,
@ -213,18 +207,6 @@ func (oqs *OutgoingQueues) SendEvent(
delete(destmap, local) delete(destmap, local)
} }
// Check if any of the destinations are prohibited by server ACLs.
for destination := range destmap {
if api.IsServerBannedFromRoom(
oqs.process.Context(),
oqs.rsAPI,
ev.RoomID().String(),
destination,
) {
delete(destmap, destination)
}
}
// If there are no remaining destinations then give up. // If there are no remaining destinations then give up.
if len(destmap) == 0 { if len(destmap) == 0 {
return nil return nil
@ -303,24 +285,6 @@ func (oqs *OutgoingQueues) SendEDU(
delete(destmap, local) delete(destmap, local)
} }
// There is absolutely no guarantee that the EDU will have a room_id
// field, as it is not required by the spec. However, if it *does*
// (e.g. typing notifications) then we should try to make sure we don't
// bother sending them to servers that are prohibited by the server
// ACLs.
if result := gjson.GetBytes(e.Content, "room_id"); result.Exists() {
for destination := range destmap {
if api.IsServerBannedFromRoom(
oqs.process.Context(),
oqs.rsAPI,
result.Str,
destination,
) {
delete(destmap, destination)
}
}
}
// If there are no remaining destinations then give up. // If there are no remaining destinations then give up.
if len(destmap) == 0 { if len(destmap) == 0 {
return nil return nil

View file

@ -34,7 +34,6 @@ import (
"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"
rsapi "github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/roomserver/types" "github.com/matrix-org/dendrite/roomserver/types"
"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/setup/process"
@ -65,15 +64,6 @@ func mustCreateFederationDatabase(t *testing.T, dbType test.DBType, realDatabase
} }
} }
type stubFederationRoomServerAPI struct {
rsapi.FederationRoomserverAPI
}
func (r *stubFederationRoomServerAPI) QueryServerBannedFromRoom(ctx context.Context, req *rsapi.QueryServerBannedFromRoomRequest, res *rsapi.QueryServerBannedFromRoomResponse) error {
res.Banned = false
return nil
}
type stubFederationClient struct { type stubFederationClient struct {
fclient.FederationClient fclient.FederationClient
shouldTxSucceed bool shouldTxSucceed bool
@ -126,7 +116,6 @@ func testSetup(failuresUntilBlacklist uint32, failuresUntilAssumedOffline uint32
txCount: *atomic.NewUint32(0), txCount: *atomic.NewUint32(0),
txRelayCount: *atomic.NewUint32(0), txRelayCount: *atomic.NewUint32(0),
} }
rs := &stubFederationRoomServerAPI{}
stats := statistics.NewStatistics(db, failuresUntilBlacklist, failuresUntilAssumedOffline) stats := statistics.NewStatistics(db, failuresUntilBlacklist, failuresUntilAssumedOffline)
signingInfo := []*fclient.SigningIdentity{ signingInfo := []*fclient.SigningIdentity{
@ -136,7 +125,7 @@ func testSetup(failuresUntilBlacklist uint32, failuresUntilAssumedOffline uint32
ServerName: "localhost", ServerName: "localhost",
}, },
} }
queues := NewOutgoingQueues(db, processContext, false, "localhost", fc, rs, &stats, signingInfo) queues := NewOutgoingQueues(db, processContext, false, "localhost", fc, &stats, signingInfo)
return db, fc, queues, processContext, close return db, fc, queues, processContext, close
} }

View file

@ -26,7 +26,6 @@ import (
"github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing" "github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing"
fedAPI "github.com/matrix-org/dendrite/federationapi" fedAPI "github.com/matrix-org/dendrite/federationapi"
fedInternal "github.com/matrix-org/dendrite/federationapi/internal"
"github.com/matrix-org/dendrite/federationapi/routing" "github.com/matrix-org/dendrite/federationapi/routing"
"github.com/matrix-org/dendrite/internal/caching" "github.com/matrix-org/dendrite/internal/caching"
"github.com/matrix-org/dendrite/internal/httputil" "github.com/matrix-org/dendrite/internal/httputil"
@ -67,11 +66,8 @@ func TestHandleQueryProfile(t *testing.T) {
keyRing := serverKeyAPI.KeyRing() keyRing := serverKeyAPI.KeyRing()
fedapi := fedAPI.NewInternalAPI(processCtx, cfg, cm, &natsInstance, &fedClient, nil, nil, keyRing, true) fedapi := fedAPI.NewInternalAPI(processCtx, cfg, cm, &natsInstance, &fedClient, nil, nil, keyRing, true)
userapi := fakeUserAPI{} userapi := fakeUserAPI{}
r, ok := fedapi.(*fedInternal.FederationInternalAPI)
if !ok { routing.Setup(routers, cfg, nil, fedapi, keyRing, &fedClient, &userapi, &cfg.MSCs, nil, caching.DisableMetrics)
panic("This is a programming error.")
}
routing.Setup(routers, cfg, nil, r, keyRing, &fedClient, &userapi, &cfg.MSCs, nil, caching.DisableMetrics)
handler := fedMux.Get(routing.QueryProfileRouteName).GetHandler().ServeHTTP handler := fedMux.Get(routing.QueryProfileRouteName).GetHandler().ServeHTTP
_, sk, _ := ed25519.GenerateKey(nil) _, sk, _ := ed25519.GenerateKey(nil)

View file

@ -25,7 +25,6 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing" "github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing"
fedAPI "github.com/matrix-org/dendrite/federationapi" fedAPI "github.com/matrix-org/dendrite/federationapi"
fedInternal "github.com/matrix-org/dendrite/federationapi/internal"
"github.com/matrix-org/dendrite/federationapi/routing" "github.com/matrix-org/dendrite/federationapi/routing"
"github.com/matrix-org/dendrite/internal/caching" "github.com/matrix-org/dendrite/internal/caching"
"github.com/matrix-org/dendrite/internal/httputil" "github.com/matrix-org/dendrite/internal/httputil"
@ -65,11 +64,8 @@ func TestHandleQueryDirectory(t *testing.T) {
keyRing := serverKeyAPI.KeyRing() keyRing := serverKeyAPI.KeyRing()
fedapi := fedAPI.NewInternalAPI(processCtx, cfg, cm, &natsInstance, &fedClient, nil, nil, keyRing, true) fedapi := fedAPI.NewInternalAPI(processCtx, cfg, cm, &natsInstance, &fedClient, nil, nil, keyRing, true)
userapi := fakeUserAPI{} userapi := fakeUserAPI{}
r, ok := fedapi.(*fedInternal.FederationInternalAPI)
if !ok { routing.Setup(routers, cfg, nil, fedapi, keyRing, &fedClient, &userapi, &cfg.MSCs, nil, caching.DisableMetrics)
panic("This is a programming error.")
}
routing.Setup(routers, cfg, nil, r, keyRing, &fedClient, &userapi, &cfg.MSCs, nil, caching.DisableMetrics)
handler := fedMux.Get(routing.QueryDirectoryRouteName).GetHandler().ServeHTTP handler := fedMux.Get(routing.QueryDirectoryRouteName).GetHandler().ServeHTTP
_, sk, _ := ed25519.GenerateKey(nil) _, sk, _ := ed25519.GenerateKey(nil)

View file

@ -23,7 +23,6 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing" "github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing"
fedAPI "github.com/matrix-org/dendrite/federationapi" fedAPI "github.com/matrix-org/dendrite/federationapi"
fedInternal "github.com/matrix-org/dendrite/federationapi/internal"
"github.com/matrix-org/dendrite/federationapi/routing" "github.com/matrix-org/dendrite/federationapi/routing"
"github.com/matrix-org/dendrite/internal/caching" "github.com/matrix-org/dendrite/internal/caching"
"github.com/matrix-org/dendrite/internal/httputil" "github.com/matrix-org/dendrite/internal/httputil"
@ -62,11 +61,8 @@ func TestHandleSend(t *testing.T) {
fedapi := fedAPI.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, nil, nil, nil, true) fedapi := fedAPI.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, nil, nil, nil, true)
serverKeyAPI := &signing.YggdrasilKeys{} serverKeyAPI := &signing.YggdrasilKeys{}
keyRing := serverKeyAPI.KeyRing() keyRing := serverKeyAPI.KeyRing()
r, ok := fedapi.(*fedInternal.FederationInternalAPI)
if !ok { routing.Setup(routers, cfg, nil, fedapi, keyRing, nil, nil, &cfg.MSCs, nil, caching.DisableMetrics)
panic("This is a programming error.")
}
routing.Setup(routers, cfg, nil, r, keyRing, nil, nil, &cfg.MSCs, nil, caching.DisableMetrics)
handler := fedMux.Get(routing.SendRouteName).GetHandler().ServeHTTP handler := fedMux.Get(routing.SendRouteName).GetHandler().ServeHTTP
_, sk, _ := ed25519.GenerateKey(nil) _, sk, _ := ed25519.GenerateKey(nil)

View file

@ -151,7 +151,7 @@ func (s *notaryServerKeysMetadataStatements) SelectKeys(ctx context.Context, txn
} }
results = append(results, sk) results = append(results, sk)
} }
return results, nil return results, rows.Err()
} }
func (s *notaryServerKeysMetadataStatements) DeleteOldJSONResponses(ctx context.Context, txn *sql.Tx) error { func (s *notaryServerKeysMetadataStatements) DeleteOldJSONResponses(ctx context.Context, txn *sql.Tx) error {

View file

@ -109,5 +109,5 @@ func (s *queueJSONStatements) SelectQueueJSON(
} }
blobs[nid] = blob blobs[nid] = blob
} }
return blobs, err return blobs, rows.Err()
} }

View file

@ -110,7 +110,7 @@ func (s *relayServersStatements) SelectRelayServers(
} }
result = append(result, spec.ServerName(relayServer)) result = append(result, spec.ServerName(relayServer))
} }
return result, nil return result, rows.Err()
} }
func (s *relayServersStatements) DeleteRelayServers( func (s *relayServersStatements) DeleteRelayServers(

View file

@ -94,12 +94,14 @@ func (s *serverSigningKeyStatements) BulkSelectServerKeys(
} }
defer internal.CloseAndLogIfError(ctx, rows, "bulkSelectServerKeys: rows.close() failed") defer internal.CloseAndLogIfError(ctx, rows, "bulkSelectServerKeys: rows.close() failed")
results := map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult{} results := map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult{}
var serverName string
var keyID string
var key string
var validUntilTS int64
var expiredTS int64
var vk gomatrixserverlib.VerifyKey
for rows.Next() { for rows.Next() {
var serverName string
var keyID string
var key string
var validUntilTS int64
var expiredTS int64
if err = rows.Scan(&serverName, &keyID, &validUntilTS, &expiredTS, &key); err != nil { if err = rows.Scan(&serverName, &keyID, &validUntilTS, &expiredTS, &key); err != nil {
return nil, err return nil, err
} }
@ -107,7 +109,6 @@ func (s *serverSigningKeyStatements) BulkSelectServerKeys(
ServerName: spec.ServerName(serverName), ServerName: spec.ServerName(serverName),
KeyID: gomatrixserverlib.KeyID(keyID), KeyID: gomatrixserverlib.KeyID(keyID),
} }
vk := gomatrixserverlib.VerifyKey{}
err = vk.Key.Decode(key) err = vk.Key.Decode(key)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -216,5 +216,5 @@ func joinedHostsFromStmt(
}) })
} }
return result, nil return result, rows.Err()
} }

View file

@ -154,7 +154,7 @@ func (s *notaryServerKeysMetadataStatements) SelectKeys(ctx context.Context, txn
} }
results = append(results, sk) results = append(results, sk)
} }
return results, nil return results, rows.Err()
} }
func (s *notaryServerKeysMetadataStatements) DeleteOldJSONResponses(ctx context.Context, txn *sql.Tx) error { func (s *notaryServerKeysMetadataStatements) DeleteOldJSONResponses(ctx context.Context, txn *sql.Tx) error {

View file

@ -135,5 +135,5 @@ func (s *queueJSONStatements) SelectQueueJSON(
} }
blobs[nid] = blob blobs[nid] = blob
} }
return blobs, err return blobs, rows.Err()
} }

View file

@ -109,7 +109,7 @@ func (s *relayServersStatements) SelectRelayServers(
} }
result = append(result, spec.ServerName(relayServer)) result = append(result, spec.ServerName(relayServer))
} }
return result, nil return result, rows.Err()
} }
func (s *relayServersStatements) DeleteRelayServers( func (s *relayServersStatements) DeleteRelayServers(

View file

@ -98,12 +98,13 @@ func (s *serverSigningKeyStatements) BulkSelectServerKeys(
err := sqlutil.RunLimitedVariablesQuery( err := sqlutil.RunLimitedVariablesQuery(
ctx, bulkSelectServerSigningKeysSQL, s.db, iKeyIDs, sqlutil.SQLite3MaxVariables, ctx, bulkSelectServerSigningKeysSQL, s.db, iKeyIDs, sqlutil.SQLite3MaxVariables,
func(rows *sql.Rows) error { func(rows *sql.Rows) error {
var serverName string
var keyID string
var key string
var validUntilTS int64
var expiredTS int64
var vk gomatrixserverlib.VerifyKey
for rows.Next() { for rows.Next() {
var serverName string
var keyID string
var key string
var validUntilTS int64
var expiredTS int64
if err := rows.Scan(&serverName, &keyID, &validUntilTS, &expiredTS, &key); err != nil { if err := rows.Scan(&serverName, &keyID, &validUntilTS, &expiredTS, &key); err != nil {
return fmt.Errorf("bulkSelectServerKeys: %v", err) return fmt.Errorf("bulkSelectServerKeys: %v", err)
} }
@ -111,7 +112,6 @@ func (s *serverSigningKeyStatements) BulkSelectServerKeys(
ServerName: spec.ServerName(serverName), ServerName: spec.ServerName(serverName),
KeyID: gomatrixserverlib.KeyID(keyID), KeyID: gomatrixserverlib.KeyID(keyID),
} }
vk := gomatrixserverlib.VerifyKey{}
err := vk.Key.Decode(key) err := vk.Key.Decode(key)
if err != nil { if err != nil {
return fmt.Errorf("bulkSelectServerKeys: %v", err) return fmt.Errorf("bulkSelectServerKeys: %v", err)

View file

@ -0,0 +1,116 @@
package tables_test
import (
"context"
"testing"
"time"
"github.com/matrix-org/dendrite/federationapi/storage/postgres"
"github.com/matrix-org/dendrite/federationapi/storage/sqlite3"
"github.com/matrix-org/dendrite/federationapi/storage/tables"
"github.com/matrix-org/dendrite/internal/sqlutil"
"github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/dendrite/test"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/stretchr/testify/assert"
)
func mustCreateServerKeyDB(t *testing.T, dbType test.DBType) (tables.FederationServerSigningKeys, func()) {
connStr, close := test.PrepareDBConnectionString(t, dbType)
db, err := sqlutil.Open(&config.DatabaseOptions{
ConnectionString: config.DataSource(connStr),
}, sqlutil.NewExclusiveWriter())
if err != nil {
t.Fatalf("failed to open database: %s", err)
}
var tab tables.FederationServerSigningKeys
switch dbType {
case test.DBTypePostgres:
tab, err = postgres.NewPostgresServerSigningKeysTable(db)
case test.DBTypeSQLite:
tab, err = sqlite3.NewSQLiteServerSigningKeysTable(db)
}
if err != nil {
t.Fatalf("failed to create table: %s", err)
}
return tab, close
}
func TestServerKeysTable(t *testing.T) {
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
ctx, cancel := context.WithCancel(context.Background())
tab, close := mustCreateServerKeyDB(t, dbType)
t.Cleanup(func() {
close()
cancel()
})
req := gomatrixserverlib.PublicKeyLookupRequest{
ServerName: "localhost",
KeyID: "ed25519:test",
}
expectedTimestamp := spec.AsTimestamp(time.Now().Add(time.Hour))
res := gomatrixserverlib.PublicKeyLookupResult{
VerifyKey: gomatrixserverlib.VerifyKey{Key: make(spec.Base64Bytes, 0)},
ExpiredTS: 0,
ValidUntilTS: expectedTimestamp,
}
// Insert the key
err := tab.UpsertServerKeys(ctx, nil, req, res)
assert.NoError(t, err)
selectKeys := map[gomatrixserverlib.PublicKeyLookupRequest]spec.Timestamp{
req: spec.AsTimestamp(time.Now()),
}
gotKeys, err := tab.BulkSelectServerKeys(ctx, nil, selectKeys)
assert.NoError(t, err)
// Now we should have a key for the req above
assert.NotNil(t, gotKeys[req])
assert.Equal(t, res, gotKeys[req])
// "Expire" the key by setting ExpireTS to a non-zero value and ValidUntilTS to 0
expectedTimestamp = spec.AsTimestamp(time.Now())
res.ExpiredTS = expectedTimestamp
res.ValidUntilTS = 0
// Update the key
err = tab.UpsertServerKeys(ctx, nil, req, res)
assert.NoError(t, err)
gotKeys, err = tab.BulkSelectServerKeys(ctx, nil, selectKeys)
assert.NoError(t, err)
// The key should be expired
assert.NotNil(t, gotKeys[req])
assert.Equal(t, res, gotKeys[req])
// Upsert a different key to validate querying multiple keys
req2 := gomatrixserverlib.PublicKeyLookupRequest{
ServerName: "notlocalhost",
KeyID: "ed25519:test2",
}
expectedTimestamp2 := spec.AsTimestamp(time.Now().Add(time.Hour))
res2 := gomatrixserverlib.PublicKeyLookupResult{
VerifyKey: gomatrixserverlib.VerifyKey{Key: make(spec.Base64Bytes, 0)},
ExpiredTS: 0,
ValidUntilTS: expectedTimestamp2,
}
err = tab.UpsertServerKeys(ctx, nil, req2, res2)
assert.NoError(t, err)
// Select multiple keys
selectKeys[req2] = spec.AsTimestamp(time.Now())
gotKeys, err = tab.BulkSelectServerKeys(ctx, nil, selectKeys)
assert.NoError(t, err)
// We now should receive two keys, one of which is expired
assert.Equal(t, 2, len(gotKeys))
assert.Equal(t, res2, gotKeys[req2])
assert.Equal(t, res, gotKeys[req])
})
}

8
go.mod
View file

@ -9,7 +9,7 @@ require (
github.com/blevesearch/bleve/v2 v2.3.8 github.com/blevesearch/bleve/v2 v2.3.8
github.com/codeclysm/extract v2.2.0+incompatible github.com/codeclysm/extract v2.2.0+incompatible
github.com/dgraph-io/ristretto v0.1.1 github.com/dgraph-io/ristretto v0.1.1
github.com/docker/docker v24.0.5+incompatible github.com/docker/docker v24.0.7+incompatible
github.com/docker/go-connections v0.4.0 github.com/docker/go-connections v0.4.0
github.com/getsentry/sentry-go v0.14.0 github.com/getsentry/sentry-go v0.14.0
github.com/go-ldap/ldap/v3 v3.4.6 github.com/go-ldap/ldap/v3 v3.4.6
@ -22,7 +22,7 @@ require (
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/matrix-org/dugong v0.0.0-20210921133753-66e6b1c67e2e github.com/matrix-org/dugong v0.0.0-20210921133753-66e6b1c67e2e
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530 github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530
github.com/matrix-org/gomatrixserverlib v0.0.0-20231024124730-58af9a2712ca github.com/matrix-org/gomatrixserverlib v0.0.0-20231212115925-41497b7563eb
github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 github.com/matrix-org/util v0.0.0-20221111132719-399730281e66
github.com/matryer/is v1.4.1 github.com/matryer/is v1.4.1
github.com/mattn/go-sqlite3 v1.14.17 github.com/mattn/go-sqlite3 v1.14.17
@ -44,7 +44,7 @@ require (
go.uber.org/atomic v1.10.0 go.uber.org/atomic v1.10.0
golang.org/x/crypto v0.14.0 golang.org/x/crypto v0.14.0
golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819
golang.org/x/image v0.5.0 golang.org/x/image v0.10.0
golang.org/x/mobile v0.0.0-20221020085226-b36e6246172e golang.org/x/mobile v0.0.0-20221020085226-b36e6246172e
golang.org/x/sync v0.3.0 golang.org/x/sync v0.3.0
golang.org/x/term v0.13.0 golang.org/x/term v0.13.0
@ -104,7 +104,7 @@ require (
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/mschoch/smat v0.2.0 // indirect github.com/mschoch/smat v0.2.0 // indirect
github.com/nats-io/jwt/v2 v2.5.0 // indirect github.com/nats-io/jwt/v2 v2.5.0 // indirect
github.com/nats-io/nkeys v0.4.4 // indirect github.com/nats-io/nkeys v0.4.6 // indirect
github.com/nats-io/nuid v1.0.1 // indirect github.com/nats-io/nuid v1.0.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect

17
go.sum
View file

@ -93,8 +93,8 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczC
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY= github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@ -185,8 +185,8 @@ github.com/matrix-org/dugong v0.0.0-20210921133753-66e6b1c67e2e h1:DP5RC0Z3XdyBE
github.com/matrix-org/dugong v0.0.0-20210921133753-66e6b1c67e2e/go.mod h1:NgPCr+UavRGH6n5jmdX8DuqFZ4JiCWIJoZiuhTRLSUg= github.com/matrix-org/dugong v0.0.0-20210921133753-66e6b1c67e2e/go.mod h1:NgPCr+UavRGH6n5jmdX8DuqFZ4JiCWIJoZiuhTRLSUg=
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530 h1:kHKxCOLcHH8r4Fzarl4+Y3K5hjothkVW5z7T1dUM11U= github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530 h1:kHKxCOLcHH8r4Fzarl4+Y3K5hjothkVW5z7T1dUM11U=
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s= github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s=
github.com/matrix-org/gomatrixserverlib v0.0.0-20231024124730-58af9a2712ca h1:JCP72vU4Vcmur2071RwYVOSoekR+ZjbC03wZD5lAAK0= github.com/matrix-org/gomatrixserverlib v0.0.0-20231212115925-41497b7563eb h1:Nn+Fr96oi7bIfdOwX5A2L6A2MZCM+lqwLe4/+3+nYj8=
github.com/matrix-org/gomatrixserverlib v0.0.0-20231024124730-58af9a2712ca/go.mod h1:M8m7seOroO5ePlgxA7AFZymnG90Cnh94rYQyngSrZkk= github.com/matrix-org/gomatrixserverlib v0.0.0-20231212115925-41497b7563eb/go.mod h1:M8m7seOroO5ePlgxA7AFZymnG90Cnh94rYQyngSrZkk=
github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 h1:6z4KxomXSIGWqhHcfzExgkH3Z3UkIXry4ibJS4Aqz2Y= github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 h1:6z4KxomXSIGWqhHcfzExgkH3Z3UkIXry4ibJS4Aqz2Y=
github.com/matrix-org/util v0.0.0-20221111132719-399730281e66/go.mod h1:iBI1foelCqA09JJgPV0FYz4qA5dUXYOxMi57FxKBdd4= github.com/matrix-org/util v0.0.0-20221111132719-399730281e66/go.mod h1:iBI1foelCqA09JJgPV0FYz4qA5dUXYOxMi57FxKBdd4=
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
@ -223,8 +223,8 @@ github.com/nats-io/nats-server/v2 v2.9.23 h1:6Wj6H6QpP9FMlpCyWUaNu2yeZ/qGj+mdRkZ
github.com/nats-io/nats-server/v2 v2.9.23/go.mod h1:wEjrEy9vnqIGE4Pqz4/c75v9Pmaq7My2IgFmnykc4C0= github.com/nats-io/nats-server/v2 v2.9.23/go.mod h1:wEjrEy9vnqIGE4Pqz4/c75v9Pmaq7My2IgFmnykc4C0=
github.com/nats-io/nats.go v1.28.0 h1:Th4G6zdsz2d0OqXdfzKLClo6bOfoI/b1kInhRtFIy5c= github.com/nats-io/nats.go v1.28.0 h1:Th4G6zdsz2d0OqXdfzKLClo6bOfoI/b1kInhRtFIy5c=
github.com/nats-io/nats.go v1.28.0/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc= github.com/nats-io/nats.go v1.28.0/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc=
github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA= github.com/nats-io/nkeys v0.4.6 h1:IzVe95ru2CT6ta874rt9saQRkWfe2nFj1NtvYSLqMzY=
github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64= github.com/nats-io/nkeys v0.4.6/go.mod h1:4DxZNzenSVd1cYQoAa8948QY3QDjrHfcfVADymtkpts=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/neilalexander/utp v0.1.1-0.20210727203401-54ae7b1cd5f9 h1:lrVQzBtkeQEGGYUHwSX1XPe1E5GL6U3KYCNe2G4bncQ= github.com/neilalexander/utp v0.1.1-0.20210727203401-54ae7b1cd5f9 h1:lrVQzBtkeQEGGYUHwSX1XPe1E5GL6U3KYCNe2G4bncQ=
@ -328,8 +328,8 @@ golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819/go.mod h1:FXUEEKJgO7OQYeo8N0
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI= golang.org/x/image v0.10.0 h1:gXjUUtwtx5yOE0VKWq1CH4IJAClq4UGgUA3i+rpON9M=
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= golang.org/x/image v0.10.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20221020085226-b36e6246172e h1:zSgtO19fpg781xknwqiQPmOHaASr6E7ZVlTseLd9Fx4= golang.org/x/mobile v0.0.0-20221020085226-b36e6246172e h1:zSgtO19fpg781xknwqiQPmOHaASr6E7ZVlTseLd9Fx4=
golang.org/x/mobile v0.0.0-20221020085226-b36e6246172e/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY= golang.org/x/mobile v0.0.0-20221020085226-b36e6246172e/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
@ -395,6 +395,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=

View file

@ -1,7 +1,7 @@
apiVersion: v2 apiVersion: v2
name: dendrite name: dendrite
version: "0.13.5" version: "0.13.6"
appVersion: "0.13.4" appVersion: "0.13.5"
description: Dendrite Matrix Homeserver description: Dendrite Matrix Homeserver
type: application type: application
keywords: keywords:

View file

@ -1,7 +1,7 @@
# dendrite # dendrite
![Version: 0.13.5](https://img.shields.io/badge/Version-0.13.5-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.13.4](https://img.shields.io/badge/AppVersion-0.13.4-informational?style=flat-square) ![Version: 0.13.6](https://img.shields.io/badge/Version-0.13.6-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.13.5](https://img.shields.io/badge/AppVersion-0.13.5-informational?style=flat-square)
Dendrite Matrix Homeserver Dendrite Matrix Homeserver
Status: **NOT PRODUCTION READY** Status: **NOT PRODUCTION READY**

View file

@ -119,7 +119,7 @@
"refId": "A" "refId": "A"
} }
], ],
"title": "Registerd Users", "title": "Registered Users",
"type": "stat" "type": "stat"
}, },
{ {

View file

@ -20,6 +20,9 @@ import (
"net/http" "net/http"
"regexp" "regexp"
"github.com/matrix-org/dendrite/clientapi/userutil"
"github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/spec" "github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util" "github.com/matrix-org/util"
) )
@ -100,10 +103,139 @@ func UsernameResponse(err error) *util.JSONResponse {
// ValidateApplicationServiceUsername returns an error if the username is invalid for an application service // ValidateApplicationServiceUsername returns an error if the username is invalid for an application service
func ValidateApplicationServiceUsername(localpart string, domain spec.ServerName) error { func ValidateApplicationServiceUsername(localpart string, domain spec.ServerName) error {
if id := fmt.Sprintf("@%s:%s", localpart, domain); len(id) > maxUsernameLength { userID := userutil.MakeUserID(localpart, domain)
return ValidateApplicationServiceUserID(userID)
}
func ValidateApplicationServiceUserID(userID string) error {
if len(userID) > maxUsernameLength {
return ErrUsernameTooLong return ErrUsernameTooLong
} else if !validUsernameRegex.MatchString(localpart) { }
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
if err != nil || !validUsernameRegex.MatchString(localpart) {
return ErrUsernameInvalid return ErrUsernameInvalid
} }
return nil return nil
} }
// userIDIsWithinApplicationServiceNamespace checks to see if a given userID
// falls within any of the namespaces of a given Application Service. If no
// Application Service is given, it will check to see if it matches any
// Application Service's namespace.
func userIDIsWithinApplicationServiceNamespace(
cfg *config.ClientAPI,
userID string,
appservice *config.ApplicationService,
) bool {
var localpart, domain, err = gomatrixserverlib.SplitID('@', userID)
if err != nil {
// Not a valid userID
return false
}
if !cfg.Matrix.IsLocalServerName(domain) {
// This is a federated userID
return false
}
if localpart == appservice.SenderLocalpart {
// This is the application service bot userID
return true
}
// Loop through given application service's namespaces and see if any match
for _, namespace := range appservice.NamespaceMap["users"] {
// Application service namespaces are checked for validity in config
if namespace.RegexpObject.MatchString(userID) {
return true
}
}
return false
}
// usernameMatchesMultipleExclusiveNamespaces will check if a given username matches
// more than one exclusive namespace. More than one is not allowed
func userIDMatchesMultipleExclusiveNamespaces(
cfg *config.ClientAPI,
userID string,
) bool {
// Check namespaces and see if more than one match
matchCount := 0
for _, appservice := range cfg.Derived.ApplicationServices {
if appservice.OwnsNamespaceCoveringUserId(userID) {
if matchCount++; matchCount > 1 {
return true
}
}
}
return false
}
// ValidateApplicationServiceRequest checks if a provided application service
// token corresponds to one that is registered, and, if so, checks if the
// supplied userIDOrLocalpart is within that application service's namespace.
//
// As long as these two requirements are met, the matched application service
// ID will be returned. Otherwise, it will return a JSON response with the
// appropriate error message.
func ValidateApplicationServiceRequest(
cfg *config.ClientAPI,
userIDOrLocalpart string,
accessToken string,
) (string, *util.JSONResponse) {
localpart, domain, err := userutil.ParseUsernameParam(userIDOrLocalpart, cfg.Matrix)
if err != nil {
return "", &util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: spec.InvalidUsername(err.Error()),
}
}
userID := userutil.MakeUserID(localpart, domain)
// Check if the token if the application service is valid with one we have
// registered in the config.
var matchedApplicationService *config.ApplicationService
for _, appservice := range cfg.Derived.ApplicationServices {
if appservice.ASToken == accessToken {
matchedApplicationService = &appservice
break
}
}
if matchedApplicationService == nil {
return "", &util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: spec.UnknownToken("Supplied access_token does not match any known application service"),
}
}
// Ensure the desired username is within at least one of the application service's namespaces.
if !userIDIsWithinApplicationServiceNamespace(cfg, userID, matchedApplicationService) {
// If we didn't find any matches, return M_EXCLUSIVE
return "", &util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.ASExclusive(fmt.Sprintf(
"Supplied username %s did not match any namespaces for application service ID: %s", userIDOrLocalpart, matchedApplicationService.ID)),
}
}
// Check this user does not fit multiple application service namespaces
if userIDMatchesMultipleExclusiveNamespaces(cfg, userID) {
return "", &util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.ASExclusive(fmt.Sprintf(
"Supplied username %s matches multiple exclusive application service namespaces. Only 1 match allowed", userIDOrLocalpart)),
}
}
// Check username application service is trying to register is valid
if err := ValidateApplicationServiceUserID(userID); err != nil {
return "", UsernameResponse(err)
}
// No errors, registration valid
return matchedApplicationService.ID, nil
}

View file

@ -3,9 +3,11 @@ package internal
import ( import (
"net/http" "net/http"
"reflect" "reflect"
"regexp"
"strings" "strings"
"testing" "testing"
"github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/gomatrixserverlib/spec" "github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util" "github.com/matrix-org/util"
) )
@ -38,7 +40,7 @@ func Test_validatePassword(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
gotErr := ValidatePassword(tt.password) gotErr := ValidatePassword(tt.password)
if !reflect.DeepEqual(gotErr, tt.wantError) { if !reflect.DeepEqual(gotErr, tt.wantError) {
t.Errorf("validatePassword() = %v, wantJSON %v", gotErr, tt.wantError) t.Errorf("validatePassword() = %v, wantError %v", gotErr, tt.wantError)
} }
if got := PasswordResponse(gotErr); !reflect.DeepEqual(got, tt.wantJSON) { if got := PasswordResponse(gotErr); !reflect.DeepEqual(got, tt.wantJSON) {
@ -167,3 +169,133 @@ func Test_validateUsername(t *testing.T) {
}) })
} }
} }
// This method tests validation of the provided Application Service token and
// username that they're registering
func TestValidateApplicationServiceRequest(t *testing.T) {
// Create a fake application service
regex := "@_appservice_.*"
fakeNamespace := config.ApplicationServiceNamespace{
Exclusive: true,
Regex: regex,
RegexpObject: regexp.MustCompile(regex),
}
fakeSenderLocalpart := "_appservice_bot"
fakeApplicationService := config.ApplicationService{
ID: "FakeAS",
URL: "null",
ASToken: "1234",
HSToken: "4321",
SenderLocalpart: fakeSenderLocalpart,
NamespaceMap: map[string][]config.ApplicationServiceNamespace{
"users": {fakeNamespace},
},
}
// Create a second fake application service where userIDs ending in
// "_overlap" overlap with the first.
regex = "@_.*_overlap"
fakeNamespace = config.ApplicationServiceNamespace{
Exclusive: true,
Regex: regex,
RegexpObject: regexp.MustCompile(regex),
}
fakeApplicationServiceOverlap := config.ApplicationService{
ID: "FakeASOverlap",
URL: fakeApplicationService.URL,
ASToken: fakeApplicationService.ASToken,
HSToken: fakeApplicationService.HSToken,
SenderLocalpart: "_appservice_bot_overlap",
NamespaceMap: map[string][]config.ApplicationServiceNamespace{
"users": {fakeNamespace},
},
}
// Set up a config
fakeConfig := &config.Dendrite{}
fakeConfig.Defaults(config.DefaultOpts{
Generate: true,
})
fakeConfig.Global.ServerName = "localhost"
fakeConfig.ClientAPI.Derived.ApplicationServices = []config.ApplicationService{fakeApplicationService, fakeApplicationServiceOverlap}
tests := []struct {
name string
localpart string
asToken string
wantError bool
wantASID string
}{
// Access token is correct, userID omitted so we are acting as SenderLocalpart
{
name: "correct access token but omitted userID",
localpart: fakeSenderLocalpart,
asToken: fakeApplicationService.ASToken,
wantError: false,
wantASID: fakeApplicationService.ID,
},
// Access token is incorrect, userID omitted so we are acting as SenderLocalpart
{
name: "incorrect access token but omitted userID",
localpart: fakeSenderLocalpart,
asToken: "xxxx",
wantError: true,
wantASID: "",
},
// Access token is correct, acting as valid userID
{
name: "correct access token and valid userID",
localpart: "_appservice_bob",
asToken: fakeApplicationService.ASToken,
wantError: false,
wantASID: fakeApplicationService.ID,
},
// Access token is correct, acting as invalid userID
{
name: "correct access token but invalid userID",
localpart: "_something_else",
asToken: fakeApplicationService.ASToken,
wantError: true,
wantASID: "",
},
// Access token is correct, acting as userID that matches two exclusive namespaces
{
name: "correct access token but non-exclusive userID",
localpart: "_appservice_overlap",
asToken: fakeApplicationService.ASToken,
wantError: true,
wantASID: "",
},
// Access token is correct, acting as matching userID that is too long
{
name: "correct access token but too long userID",
localpart: "_appservice_" + strings.Repeat("a", maxUsernameLength),
asToken: fakeApplicationService.ASToken,
wantError: true,
wantASID: "",
},
// Access token is correct, acting as userID that matches but is invalid
{
name: "correct access token and matching but invalid userID",
localpart: "@_appservice_bob::",
asToken: fakeApplicationService.ASToken,
wantError: true,
wantASID: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotASID, gotResp := ValidateApplicationServiceRequest(&fakeConfig.ClientAPI, tt.localpart, tt.asToken)
if tt.wantError && gotResp == nil {
t.Error("expected an error, but succeeded")
}
if !tt.wantError && gotResp != nil {
t.Errorf("expected success, but returned error: %v", *gotResp)
}
if gotASID != tt.wantASID {
t.Errorf("returned '%s', but expected '%s'", gotASID, tt.wantASID)
}
})
}
}

View file

@ -18,7 +18,7 @@ var build string
const ( const (
VersionMajor = 0 VersionMajor = 0
VersionMinor = 13 VersionMinor = 13
VersionPatch = 4 VersionPatch = 5
VersionTag = "" // example: "rc1" VersionTag = "" // example: "rc1"
gitRevLen = 7 // 7 matches the displayed characters on github.com gitRevLen = 7 // 7 matches the displayed characters on github.com

View file

@ -63,6 +63,40 @@ type downloadRequest struct {
DownloadFilename string DownloadFilename string
} }
// Taken from: https://github.com/matrix-org/synapse/blob/c3627d0f99ed5a23479305dc2bd0e71ca25ce2b1/synapse/media/_base.py#L53C1-L84
// A list of all content types that are "safe" to be rendered inline in a browser.
var allowInlineTypes = map[types.ContentType]struct{}{
"text/css": {},
"text/plain": {},
"text/csv": {},
"application/json": {},
"application/ld+json": {},
// We allow some media files deemed as safe, which comes from the matrix-react-sdk.
// https://github.com/matrix-org/matrix-react-sdk/blob/a70fcfd0bcf7f8c85986da18001ea11597989a7c/src/utils/blobs.ts#L51
// SVGs are *intentionally* omitted.
"image/jpeg": {},
"image/gif": {},
"image/png": {},
"image/apng": {},
"image/webp": {},
"image/avif": {},
"video/mp4": {},
"video/webm": {},
"video/ogg": {},
"video/quicktime": {},
"audio/mp4": {},
"audio/webm": {},
"audio/aac": {},
"audio/mpeg": {},
"audio/ogg": {},
"audio/wave": {},
"audio/wav": {},
"audio/x-wav": {},
"audio/x-pn-wav": {},
"audio/flac": {},
"audio/x-flac": {},
}
// Download implements GET /download and GET /thumbnail // Download implements GET /download and GET /thumbnail
// Files from this server (i.e. origin == cfg.ServerName) are served directly // Files from this server (i.e. origin == cfg.ServerName) are served directly
// Files from remote servers (i.e. origin != cfg.ServerName) are cached locally. // Files from remote servers (i.e. origin != cfg.ServerName) are cached locally.
@ -353,7 +387,7 @@ func (r *downloadRequest) addDownloadFilenameToHeaders(
} }
if len(filename) == 0 { if len(filename) == 0 {
w.Header().Set("Content-Disposition", "attachment") w.Header().Set("Content-Disposition", contentDispositionFor(""))
return nil return nil
} }
@ -383,20 +417,21 @@ func (r *downloadRequest) addDownloadFilenameToHeaders(
unescaped = strings.ReplaceAll(unescaped, `\`, `\\"`) unescaped = strings.ReplaceAll(unescaped, `\`, `\\"`)
unescaped = strings.ReplaceAll(unescaped, `"`, `\"`) unescaped = strings.ReplaceAll(unescaped, `"`, `\"`)
disposition := contentDispositionFor(responseMetadata.ContentType)
if isASCII { if isASCII {
// For ASCII filenames, we should only quote the filename if // For ASCII filenames, we should only quote the filename if
// it needs to be done, e.g. it contains a space or a character // it needs to be done, e.g. it contains a space or a character
// that would otherwise be parsed as a control character in the // that would otherwise be parsed as a control character in the
// Content-Disposition header // Content-Disposition header
w.Header().Set("Content-Disposition", fmt.Sprintf( w.Header().Set("Content-Disposition", fmt.Sprintf(
`attachment; filename=%s%s%s`, `%s; filename=%s%s%s`,
quote, unescaped, quote, disposition, quote, unescaped, quote,
)) ))
} else { } else {
// For UTF-8 filenames, we quote always, as that's the standard // For UTF-8 filenames, we quote always, as that's the standard
w.Header().Set("Content-Disposition", fmt.Sprintf( w.Header().Set("Content-Disposition", fmt.Sprintf(
`attachment; filename*=utf-8''%s`, `%s; filename*=utf-8''%s`,
url.QueryEscape(unescaped), disposition, url.QueryEscape(unescaped),
)) ))
} }
@ -808,3 +843,12 @@ func (r *downloadRequest) fetchRemoteFile(
return types.Path(finalPath), duplicate, nil return types.Path(finalPath), duplicate, nil
} }
// contentDispositionFor returns the Content-Disposition for a given
// content type.
func contentDispositionFor(contentType types.ContentType) string {
if _, ok := allowInlineTypes[contentType]; ok {
return "inline"
}
return "attachment"
}

View file

@ -0,0 +1,13 @@
package routing
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_dispositionFor(t *testing.T) {
assert.Equal(t, "attachment", contentDispositionFor(""), "empty content type")
assert.Equal(t, "attachment", contentDispositionFor("image/svg"), "image/svg")
assert.Equal(t, "inline", contentDispositionFor("image/jpeg"), "image/jpg")
}

View file

@ -29,6 +29,8 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
const MRoomServerACL = "m.room.server_acl"
type ServerACLDatabase interface { type ServerACLDatabase interface {
// GetKnownRooms returns a list of all rooms we know about. // GetKnownRooms returns a list of all rooms we know about.
GetKnownRooms(ctx context.Context) ([]string, error) GetKnownRooms(ctx context.Context) ([]string, error)
@ -57,7 +59,7 @@ func NewServerACLs(db ServerACLDatabase) *ServerACLs {
// do then we'll process it into memory so that we have the regexes to // do then we'll process it into memory so that we have the regexes to
// hand. // hand.
for _, room := range rooms { for _, room := range rooms {
state, err := db.GetStateEvent(ctx, room, "m.room.server_acl", "") state, err := db.GetStateEvent(ctx, room, MRoomServerACL, "")
if err != nil { if err != nil {
logrus.WithError(err).Errorf("Failed to get server ACLs for room %q", room) logrus.WithError(err).Errorf("Failed to get server ACLs for room %q", room)
continue continue

View file

@ -33,6 +33,7 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/matrix-org/dendrite/roomserver/acls"
"github.com/matrix-org/dendrite/roomserver/internal/helpers" "github.com/matrix-org/dendrite/roomserver/internal/helpers"
userAPI "github.com/matrix-org/dendrite/userapi/api" userAPI "github.com/matrix-org/dendrite/userapi/api"
@ -491,6 +492,27 @@ func (r *Inputer) processRoomEvent(
} }
} }
// If this is a membership event, it is possible we newly joined a federated room and eventually
// missed to update our m.room.server_acl - the following ensures we set the ACLs
// TODO: This probably performs badly in benchmarks
if event.Type() == spec.MRoomMember {
membership, _ := event.Membership()
if membership == spec.Join {
_, serverName, _ := gomatrixserverlib.SplitID('@', *event.StateKey())
// only handle local membership events
if r.Cfg.Matrix.IsLocalServerName(serverName) {
var aclEvent *types.HeaderedEvent
aclEvent, err = r.DB.GetStateEvent(ctx, event.RoomID().String(), acls.MRoomServerACL, "")
if err != nil {
logrus.WithError(err).Error("failed to get server ACLs")
}
if aclEvent != nil {
r.ACLs.OnServerACLUpdate(aclEvent)
}
}
}
}
// Handle remote room upgrades, e.g. remove published room // Handle remote room upgrades, e.g. remove published room
if event.Type() == "m.room.tombstone" && event.StateKeyEquals("") && !r.Cfg.Matrix.IsLocalServerName(senderDomain) { if event.Type() == "m.room.tombstone" && event.StateKeyEquals("") && !r.Cfg.Matrix.IsLocalServerName(senderDomain) {
if err = r.handleRemoteRoomUpgrade(ctx, event); err != nil { if err = r.handleRemoteRoomUpgrade(ctx, event); err != nil {

View file

@ -278,16 +278,11 @@ func (r *Queryer) queryMembershipForOptionalSenderID(ctx context.Context, roomID
return nil return nil
} }
membershipEventNID, membershipState, stillInRoom, isRoomforgotten, err := r.DB.GetMembership(ctx, info.RoomNID, *senderID) membershipEventNID, stillInRoom, isRoomforgotten, err := r.DB.GetMembership(ctx, info.RoomNID, *senderID)
if err != nil { if err != nil {
return err return err
} }
if membershipState == tables.MembershipStateInvite {
response.Membership = spec.Invite
response.IsInRoom = true
}
response.IsRoomForgotten = isRoomforgotten response.IsRoomForgotten = isRoomforgotten
if membershipEventNID == 0 { if membershipEventNID == 0 {
@ -446,7 +441,7 @@ func (r *Queryer) QueryMembershipsForRoom(
return nil return nil
} }
membershipEventNID, _, stillInRoom, isRoomforgotten, err := r.DB.GetMembership(ctx, info.RoomNID, request.SenderID) membershipEventNID, stillInRoom, isRoomforgotten, err := r.DB.GetMembership(ctx, info.RoomNID, request.SenderID)
if err != nil { if err != nil {
return err return err
} }
@ -994,7 +989,7 @@ func (r *Queryer) CurrentStateEvent(ctx context.Context, roomID spec.RoomID, eve
} }
func (r *Queryer) UserJoinedToRoom(ctx context.Context, roomNID types.RoomNID, senderID spec.SenderID) (bool, error) { func (r *Queryer) UserJoinedToRoom(ctx context.Context, roomNID types.RoomNID, senderID spec.SenderID) (bool, error) {
_, _, isIn, _, err := r.DB.GetMembership(ctx, roomNID, senderID) _, isIn, _, err := r.DB.GetMembership(ctx, roomNID, senderID)
return isIn, err return isIn, err
} }

View file

@ -17,6 +17,7 @@ package query
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"sort" "sort"
@ -56,6 +57,12 @@ func (querier *Queryer) QueryNextRoomHierarchyPage(ctx context.Context, walker r
break break
} }
// If the context is canceled, we might still have discovered rooms
// return them to the client and let the client know there _may_ be more rooms.
if errors.Is(ctx.Err(), context.Canceled) {
break
}
// pop the stack // pop the stack
queuedRoom := unvisited[len(unvisited)-1] queuedRoom := unvisited[len(unvisited)-1]
unvisited = unvisited[:len(unvisited)-1] unvisited = unvisited[:len(unvisited)-1]
@ -112,6 +119,11 @@ func (querier *Queryer) QueryNextRoomHierarchyPage(ctx context.Context, walker r
pubRoom := publicRoomsChunk(ctx, querier, queuedRoom.RoomID) pubRoom := publicRoomsChunk(ctx, querier, queuedRoom.RoomID)
if pubRoom == nil {
util.GetLogger(ctx).WithField("room_id", queuedRoom.RoomID).Debug("unable to get publicRoomsChunk")
continue
}
discoveredRooms = append(discoveredRooms, fclient.RoomHierarchyRoom{ discoveredRooms = append(discoveredRooms, fclient.RoomHierarchyRoom{
PublicRoom: *pubRoom, PublicRoom: *pubRoom,
RoomType: roomType, RoomType: roomType,

View file

@ -73,7 +73,7 @@ func (r *RoomEventProducer) ProduceRoomEvents(roomID string, updates []api.Outpu
} }
} }
if eventType == "m.room.server_acl" && update.NewRoomEvent.Event.StateKeyEquals("") { if eventType == acls.MRoomServerACL && update.NewRoomEvent.Event.StateKeyEquals("") {
ev := update.NewRoomEvent.Event.PDU ev := update.NewRoomEvent.Event.PDU
defer r.ACLs.OnServerACLUpdate(ev) defer r.ACLs.OnServerACLUpdate(ev)
} }

View file

@ -7,6 +7,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/matrix-org/dendrite/federationapi/statistics"
"github.com/matrix-org/dendrite/internal/caching" "github.com/matrix-org/dendrite/internal/caching"
"github.com/matrix-org/dendrite/internal/eventutil" "github.com/matrix-org/dendrite/internal/eventutil"
"github.com/matrix-org/dendrite/internal/httputil" "github.com/matrix-org/dendrite/internal/httputil"
@ -15,6 +16,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"github.com/matrix-org/dendrite/roomserver/acls"
"github.com/matrix-org/dendrite/roomserver/state" "github.com/matrix-org/dendrite/roomserver/state"
"github.com/matrix-org/dendrite/roomserver/types" "github.com/matrix-org/dendrite/roomserver/types"
"github.com/matrix-org/dendrite/userapi" "github.com/matrix-org/dendrite/userapi"
@ -34,6 +36,10 @@ import (
"github.com/matrix-org/dendrite/test/testrig" "github.com/matrix-org/dendrite/test/testrig"
) )
var testIsBlacklistedOrBackingOff = func(s spec.ServerName) (*statistics.ServerStatistics, error) {
return &statistics.ServerStatistics{}, nil
}
type FakeQuerier struct { type FakeQuerier struct {
api.QuerySenderIDAPI api.QuerySenderIDAPI
} }
@ -58,7 +64,7 @@ func TestUsers(t *testing.T) {
}) })
t.Run("kick users", func(t *testing.T) { t.Run("kick users", func(t *testing.T) {
usrAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) usrAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
rsAPI.SetUserAPI(usrAPI) rsAPI.SetUserAPI(usrAPI)
testKickUsers(t, rsAPI, usrAPI) testKickUsers(t, rsAPI, usrAPI)
}) })
@ -258,7 +264,7 @@ func TestPurgeRoom(t *testing.T) {
fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, rsAPI, caches, nil, true) fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, rsAPI, caches, nil, true)
rsAPI.SetFederationAPI(fsAPI, nil) rsAPI.SetFederationAPI(fsAPI, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, fsAPI.IsBlacklistedOrBackingOff)
syncapi.AddPublicRoutes(processCtx, routers, cfg, cm, &natsInstance, userAPI, rsAPI, caches, caching.DisableMetrics) syncapi.AddPublicRoutes(processCtx, routers, cfg, cm, &natsInstance, userAPI, rsAPI, caches, caching.DisableMetrics)
// Create the room // Create the room
@ -1050,7 +1056,7 @@ func TestUpgrade(t *testing.T) {
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(nil, nil)
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
rsAPI.SetUserAPI(userAPI) rsAPI.SetUserAPI(userAPI)
for _, tc := range testCases { for _, tc := range testCases {
@ -1185,3 +1191,43 @@ func TestStateReset(t *testing.T) {
} }
}) })
} }
func TestNewServerACLs(t *testing.T) {
alice := test.NewUser(t)
roomWithACL := test.NewRoom(t, alice)
roomWithACL.CreateAndInsert(t, alice, acls.MRoomServerACL, acls.ServerACL{
Allowed: []string{"*"},
Denied: []string{"localhost"},
AllowIPLiterals: false,
}, test.WithStateKey(""))
roomWithoutACL := test.NewRoom(t, alice)
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
cfg, processCtx, closeDB := testrig.CreateConfig(t, dbType)
defer closeDB()
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
natsInstance := &jetstream.NATSInstance{}
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
// start JetStream listeners
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil)
// let the RS create the events
err := api.SendEvents(context.Background(), rsAPI, api.KindNew, roomWithACL.Events(), "test", "test", "test", nil, false)
assert.NoError(t, err)
err = api.SendEvents(context.Background(), rsAPI, api.KindNew, roomWithoutACL.Events(), "test", "test", "test", nil, false)
assert.NoError(t, err)
db, err := storage.Open(processCtx.Context(), cm, &cfg.RoomServer.Database, caches)
assert.NoError(t, err)
// create new server ACLs and verify server is banned/not banned
serverACLs := acls.NewServerACLs(db)
banned := serverACLs.IsServerBannedFromRoom("localhost", roomWithACL.ID)
assert.Equal(t, true, banned)
banned = serverACLs.IsServerBannedFromRoom("localhost", roomWithoutACL.ID)
assert.Equal(t, false, banned)
})
}

View file

@ -133,7 +133,7 @@ type Database interface {
// in this room, along a boolean set to true if the user is still in this room, // in this room, along a boolean set to true if the user is still in this room,
// false if not. // false if not.
// Returns an error if there was a problem talking to the database. // Returns an error if there was a problem talking to the database.
GetMembership(ctx context.Context, roomNID types.RoomNID, requestSenderID spec.SenderID) (membershipEventNID types.EventNID, membershipNID tables.MembershipState, stillInRoom, isRoomForgotten bool, err error) GetMembership(ctx context.Context, roomNID types.RoomNID, requestSenderID spec.SenderID) (membershipEventNID types.EventNID, stillInRoom, isRoomForgotten bool, err error)
// Lookup the membership event numeric IDs for all user that are or have // Lookup the membership event numeric IDs for all user that are or have
// been members of a given room. Only lookup events of "join" membership if // been members of a given room. Only lookup events of "join" membership if
// joinOnly is set to true. // joinOnly is set to true.

View file

@ -249,6 +249,7 @@ func (s *eventStatements) BulkSelectSnapshotsFromEventIDs(
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer internal.CloseAndLogIfError(ctx, rows, "BulkSelectSnapshotsFromEventIDs: rows.close() failed")
var eventID string var eventID string
var stateNID types.StateSnapshotNID var stateNID types.StateSnapshotNID
@ -563,7 +564,7 @@ func (s *eventStatements) SelectRoomNIDsForEventNIDs(
} }
result[eventNID] = roomNID result[eventNID] = roomNID
} }
return result, nil return result, rows.Err()
} }
func eventNIDsAsArray(eventNIDs []types.EventNID) pq.Int64Array { func eventNIDsAsArray(eventNIDs []types.EventNID) pq.Int64Array {

View file

@ -363,7 +363,7 @@ func (s *membershipStatements) SelectRoomsWithMembership(
} }
roomNIDs = append(roomNIDs, roomNID) roomNIDs = append(roomNIDs, roomNID)
} }
return roomNIDs, nil return roomNIDs, rows.Err()
} }
func (s *membershipStatements) SelectJoinedUsersSetForRooms( func (s *membershipStatements) SelectJoinedUsersSetForRooms(

View file

@ -137,7 +137,7 @@ func (s *roomStatements) SelectRoomIDsWithEvents(ctx context.Context, txn *sql.T
} }
roomIDs = append(roomIDs, roomID) roomIDs = append(roomIDs, roomID)
} }
return roomIDs, nil return roomIDs, rows.Err()
} }
func (s *roomStatements) InsertRoomNID( func (s *roomStatements) InsertRoomNID(
ctx context.Context, txn *sql.Tx, ctx context.Context, txn *sql.Tx,
@ -255,7 +255,7 @@ func (s *roomStatements) SelectRoomVersionsForRoomNIDs(
} }
result[roomNID] = roomVersion result[roomNID] = roomVersion
} }
return result, nil return result, rows.Err()
} }
func (s *roomStatements) BulkSelectRoomIDs(ctx context.Context, txn *sql.Tx, roomNIDs []types.RoomNID) ([]string, error) { func (s *roomStatements) BulkSelectRoomIDs(ctx context.Context, txn *sql.Tx, roomNIDs []types.RoomNID) ([]string, error) {
@ -277,7 +277,7 @@ func (s *roomStatements) BulkSelectRoomIDs(ctx context.Context, txn *sql.Tx, roo
} }
roomIDs = append(roomIDs, roomID) roomIDs = append(roomIDs, roomID)
} }
return roomIDs, nil return roomIDs, rows.Err()
} }
func (s *roomStatements) BulkSelectRoomNIDs(ctx context.Context, txn *sql.Tx, roomIDs []string) ([]types.RoomNID, error) { func (s *roomStatements) BulkSelectRoomNIDs(ctx context.Context, txn *sql.Tx, roomIDs []string) ([]types.RoomNID, error) {
@ -299,7 +299,7 @@ func (s *roomStatements) BulkSelectRoomNIDs(ctx context.Context, txn *sql.Tx, ro
} }
roomNIDs = append(roomNIDs, roomNID) roomNIDs = append(roomNIDs, roomNID)
} }
return roomNIDs, nil return roomNIDs, rows.Err()
} }
func roomNIDsAsArray(roomNIDs []types.RoomNID) pq.Int64Array { func roomNIDsAsArray(roomNIDs []types.RoomNID) pq.Int64Array {

View file

@ -162,6 +162,7 @@ func (s *userRoomKeysStatements) SelectAllPublicKeysForUser(ctx context.Context,
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return nil, nil return nil, nil
} }
defer internal.CloseAndLogIfError(ctx, rows, "SelectAllPublicKeysForUser: failed to close rows")
resultMap := make(map[types.RoomNID]ed25519.PublicKey) resultMap := make(map[types.RoomNID]ed25519.PublicKey)
@ -173,5 +174,5 @@ func (s *userRoomKeysStatements) SelectAllPublicKeysForUser(ctx context.Context,
} }
resultMap[roomNID] = pubkey resultMap[roomNID] = pubkey
} }
return resultMap, err return resultMap, rows.Err()
} }

View file

@ -491,14 +491,14 @@ func (d *Database) RemoveRoomAlias(ctx context.Context, alias string) error {
}) })
} }
func (d *Database) GetMembership(ctx context.Context, roomNID types.RoomNID, requestSenderID spec.SenderID) (membershipEventNID types.EventNID, membershipState tables.MembershipState, stillInRoom, isRoomforgotten bool, err error) { func (d *Database) GetMembership(ctx context.Context, roomNID types.RoomNID, requestSenderID spec.SenderID) (membershipEventNID types.EventNID, GetMembershipstillInGetMembershipRoom, isRoomforgotten bool, err error) {
var requestSenderUserNID types.EventStateKeyNID var requestSenderUserNID types.EventStateKeyNID
err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
requestSenderUserNID, err = d.assignStateKeyNID(ctx, txn, string(requestSenderID)) requestSenderUserNID, err = d.assignStateKeyNID(ctx, txn, string(requestSenderID))
return err return err
}) })
if err != nil { if err != nil {
return 0, 0, false, false, fmt.Errorf("d.assignStateKeyNID: %w", err) return 0, false, false, fmt.Errorf("d.assignStateKeyNID: %w", err)
} }
senderMembershipEventNID, senderMembership, isRoomforgotten, err := senderMembershipEventNID, senderMembership, isRoomforgotten, err :=
@ -507,12 +507,12 @@ func (d *Database) GetMembership(ctx context.Context, roomNID types.RoomNID, req
) )
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
// The user has never been a member of that room // The user has never been a member of that room
return 0, 0, false, false, nil return 0, false, false, nil
} else if err != nil { } else if err != nil {
return return
} }
return senderMembershipEventNID, senderMembership, senderMembership == tables.MembershipStateJoin, isRoomforgotten, nil return senderMembershipEventNID, senderMembership == tables.MembershipStateJoin, isRoomforgotten, nil
} }
func (d *Database) GetMembershipEventNIDsForRoom( func (d *Database) GetMembershipEventNIDsForRoom(

View file

@ -109,5 +109,5 @@ func (s *eventJSONStatements) BulkSelectEventJSON(
} }
result.EventNID = types.EventNID(eventNID) result.EventNID = types.EventNID(eventNID)
} }
return results[:i], nil return results[:i], rows.Err()
} }

View file

@ -136,7 +136,7 @@ func (s *eventStateKeyStatements) BulkSelectEventStateKeyNID(
} }
result[stateKey] = types.EventStateKeyNID(stateKeyNID) result[stateKey] = types.EventStateKeyNID(stateKeyNID)
} }
return result, nil return result, rows.Err()
} }
func (s *eventStateKeyStatements) BulkSelectEventStateKey( func (s *eventStateKeyStatements) BulkSelectEventStateKey(
@ -167,5 +167,5 @@ func (s *eventStateKeyStatements) BulkSelectEventStateKey(
} }
result[types.EventStateKeyNID(stateKeyNID)] = stateKey result[types.EventStateKeyNID(stateKeyNID)] = stateKey
} }
return result, nil return result, rows.Err()
} }

View file

@ -147,5 +147,5 @@ func (s *eventTypeStatements) BulkSelectEventTypeNID(
} }
result[eventType] = types.EventTypeNID(eventTypeNID) result[eventType] = types.EventTypeNID(eventTypeNID)
} }
return result, nil return result, rows.Err()
} }

View file

@ -310,6 +310,9 @@ func (s *eventStatements) BulkSelectStateEventByID(
} }
results = append(results, result) results = append(results, result)
} }
if err = rows.Err(); err != nil {
return nil, err
}
if !excludeRejected && i != len(eventIDs) { if !excludeRejected && i != len(eventIDs) {
// If there are fewer rows returned than IDs then we were asked to lookup event IDs we don't have. // If there are fewer rows returned than IDs then we were asked to lookup event IDs we don't have.
// We don't know which ones were missing because we don't return the string IDs in the query. // We don't know which ones were missing because we don't return the string IDs in the query.
@ -377,7 +380,7 @@ func (s *eventStatements) BulkSelectStateEventByNID(
return nil, err return nil, err
} }
} }
return results[:i], err return results[:i], rows.Err()
} }
// bulkSelectStateAtEventByID lookups the state at a list of events by event ID. // bulkSelectStateAtEventByID lookups the state at a list of events by event ID.
@ -425,6 +428,9 @@ func (s *eventStatements) BulkSelectStateAtEventByID(
) )
} }
} }
if err = rows.Err(); err != nil {
return nil, err
}
if i != len(eventIDs) { if i != len(eventIDs) {
return nil, types.MissingEventError( return nil, types.MissingEventError(
fmt.Sprintf("storage: event IDs missing from the database (%d != %d)", i, len(eventIDs)), fmt.Sprintf("storage: event IDs missing from the database (%d != %d)", i, len(eventIDs)),
@ -507,6 +513,9 @@ func (s *eventStatements) BulkSelectStateAtEventAndReference(
result.BeforeStateSnapshotNID = types.StateSnapshotNID(stateSnapshotNID) result.BeforeStateSnapshotNID = types.StateSnapshotNID(stateSnapshotNID)
result.EventID = eventID result.EventID = eventID
} }
if err = rows.Err(); err != nil {
return nil, err
}
if i != len(eventNIDs) { if i != len(eventNIDs) {
return nil, fmt.Errorf("storage: event NIDs missing from the database (%d != %d)", i, len(eventNIDs)) return nil, fmt.Errorf("storage: event NIDs missing from the database (%d != %d)", i, len(eventNIDs))
} }
@ -544,6 +553,9 @@ func (s *eventStatements) BulkSelectEventID(ctx context.Context, txn *sql.Tx, ev
} }
results[types.EventNID(eventNID)] = eventID results[types.EventNID(eventNID)] = eventID
} }
if err = rows.Err(); err != nil {
return nil, err
}
if i != len(eventNIDs) { if i != len(eventNIDs) {
return nil, fmt.Errorf("storage: event NIDs missing from the database (%d != %d)", i, len(eventNIDs)) return nil, fmt.Errorf("storage: event NIDs missing from the database (%d != %d)", i, len(eventNIDs))
} }
@ -602,7 +614,7 @@ func (s *eventStatements) bulkSelectEventNID(ctx context.Context, txn *sql.Tx, e
RoomNID: types.RoomNID(roomNID), RoomNID: types.RoomNID(roomNID),
} }
} }
return results, nil return results, rows.Err()
} }
func (s *eventStatements) SelectMaxEventDepth(ctx context.Context, txn *sql.Tx, eventNIDs []types.EventNID) (int64, error) { func (s *eventStatements) SelectMaxEventDepth(ctx context.Context, txn *sql.Tx, eventNIDs []types.EventNID) (int64, error) {
@ -652,7 +664,7 @@ func (s *eventStatements) SelectRoomNIDsForEventNIDs(
} }
result[eventNID] = roomNID result[eventNID] = roomNID
} }
return result, nil return result, rows.Err()
} }
func eventNIDsAsArray(eventNIDs []types.EventNID) string { func eventNIDsAsArray(eventNIDs []types.EventNID) string {

View file

@ -126,6 +126,9 @@ func (s *inviteStatements) UpdateInviteRetired(
} }
eventIDs = append(eventIDs, inviteEventID) eventIDs = append(eventIDs, inviteEventID)
} }
if err = rows.Err(); err != nil {
return
}
// now retire the invites // now retire the invites
stmt = sqlutil.TxStmt(txn, s.updateInviteRetiredStmt) stmt = sqlutil.TxStmt(txn, s.updateInviteRetiredStmt)
_, err = stmt.ExecContext(ctx, roomNID, targetUserNID) _, err = stmt.ExecContext(ctx, roomNID, targetUserNID)
@ -157,5 +160,5 @@ func (s *inviteStatements) SelectInviteActiveForUserInRoom(
result = append(result, types.EventStateKeyNID(senderUserNID)) result = append(result, types.EventStateKeyNID(senderUserNID))
eventIDs = append(eventIDs, eventID) eventIDs = append(eventIDs, eventID)
} }
return result, eventIDs, eventJSON, nil return result, eventIDs, eventJSON, rows.Err()
} }

View file

@ -250,6 +250,7 @@ func (s *membershipStatements) SelectMembershipsFromRoom(
} }
eventNIDs = append(eventNIDs, eNID) eventNIDs = append(eventNIDs, eNID)
} }
err = rows.Err()
return return
} }
@ -277,6 +278,7 @@ func (s *membershipStatements) SelectMembershipsFromRoomAndMembership(
} }
eventNIDs = append(eventNIDs, eNID) eventNIDs = append(eventNIDs, eNID)
} }
err = rows.Err()
return return
} }
@ -313,7 +315,7 @@ func (s *membershipStatements) SelectRoomsWithMembership(
} }
roomNIDs = append(roomNIDs, roomNID) roomNIDs = append(roomNIDs, roomNID)
} }
return roomNIDs, nil return roomNIDs, rows.Err()
} }
func (s *membershipStatements) SelectJoinedUsersSetForRooms(ctx context.Context, txn *sql.Tx, roomNIDs []types.RoomNID, userNIDs []types.EventStateKeyNID, localOnly bool) (map[types.EventStateKeyNID]int, error) { func (s *membershipStatements) SelectJoinedUsersSetForRooms(ctx context.Context, txn *sql.Tx, roomNIDs []types.RoomNID, userNIDs []types.EventStateKeyNID, localOnly bool) (map[types.EventStateKeyNID]int, error) {

View file

@ -121,7 +121,7 @@ func (s *roomAliasesStatements) SelectAliasesFromRoomID(
aliases = append(aliases, alias) aliases = append(aliases, alias)
} }
err = rows.Err()
return return
} }

View file

@ -128,7 +128,7 @@ func (s *roomStatements) SelectRoomIDsWithEvents(ctx context.Context, txn *sql.T
} }
roomIDs = append(roomIDs, roomID) roomIDs = append(roomIDs, roomID)
} }
return roomIDs, nil return roomIDs, rows.Err()
} }
func (s *roomStatements) SelectRoomInfo(ctx context.Context, txn *sql.Tx, roomID string) (*types.RoomInfo, error) { func (s *roomStatements) SelectRoomInfo(ctx context.Context, txn *sql.Tx, roomID string) (*types.RoomInfo, error) {
@ -265,7 +265,7 @@ func (s *roomStatements) SelectRoomVersionsForRoomNIDs(
} }
result[roomNID] = roomVersion result[roomNID] = roomVersion
} }
return result, nil return result, rows.Err()
} }
func (s *roomStatements) BulkSelectRoomIDs(ctx context.Context, txn *sql.Tx, roomNIDs []types.RoomNID) ([]string, error) { func (s *roomStatements) BulkSelectRoomIDs(ctx context.Context, txn *sql.Tx, roomNIDs []types.RoomNID) ([]string, error) {
@ -293,7 +293,7 @@ func (s *roomStatements) BulkSelectRoomIDs(ctx context.Context, txn *sql.Tx, roo
} }
roomIDs = append(roomIDs, roomID) roomIDs = append(roomIDs, roomID)
} }
return roomIDs, nil return roomIDs, rows.Err()
} }
func (s *roomStatements) BulkSelectRoomNIDs(ctx context.Context, txn *sql.Tx, roomIDs []string) ([]types.RoomNID, error) { func (s *roomStatements) BulkSelectRoomNIDs(ctx context.Context, txn *sql.Tx, roomIDs []string) ([]types.RoomNID, error) {
@ -321,5 +321,5 @@ func (s *roomStatements) BulkSelectRoomNIDs(ctx context.Context, txn *sql.Tx, ro
} }
roomNIDs = append(roomNIDs, roomNID) roomNIDs = append(roomNIDs, roomNID)
} }
return roomNIDs, nil return roomNIDs, rows.Err()
} }

View file

@ -133,13 +133,16 @@ func (s *stateSnapshotStatements) BulkSelectStateBlockNIDs(
var stateBlockNIDsJSON string var stateBlockNIDsJSON string
for ; rows.Next(); i++ { for ; rows.Next(); i++ {
result := &results[i] result := &results[i]
if err := rows.Scan(&result.StateSnapshotNID, &stateBlockNIDsJSON); err != nil { if err = rows.Scan(&result.StateSnapshotNID, &stateBlockNIDsJSON); err != nil {
return nil, err return nil, err
} }
if err := json.Unmarshal([]byte(stateBlockNIDsJSON), &result.StateBlockNIDs); err != nil { if err = json.Unmarshal([]byte(stateBlockNIDsJSON), &result.StateBlockNIDs); err != nil {
return nil, err return nil, err
} }
} }
if err = rows.Err(); err != nil {
return nil, err
}
if i != len(stateNIDs) { if i != len(stateNIDs) {
return nil, types.MissingStateError(fmt.Sprintf("storage: state NIDs missing from the database (%d != %d)", i, len(stateNIDs))) return nil, types.MissingStateError(fmt.Sprintf("storage: state NIDs missing from the database (%d != %d)", i, len(stateNIDs)))
} }

View file

@ -177,6 +177,7 @@ func (s *userRoomKeysStatements) SelectAllPublicKeysForUser(ctx context.Context,
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return nil, nil return nil, nil
} }
defer internal.CloseAndLogIfError(ctx, rows, "SelectAllPublicKeysForUser: failed to close rows")
resultMap := make(map[types.RoomNID]ed25519.PublicKey) resultMap := make(map[types.RoomNID]ed25519.PublicKey)
@ -188,5 +189,5 @@ func (s *userRoomKeysStatements) SelectAllPublicKeysForUser(ctx context.Context,
} }
resultMap[roomNID] = pubkey resultMap[roomNID] = pubkey
} }
return resultMap, err return resultMap, rows.Err()
} }

View file

@ -0,0 +1,76 @@
package tables
import (
"testing"
"github.com/matrix-org/dendrite/roomserver/types"
"github.com/matrix-org/dendrite/test"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/stretchr/testify/assert"
)
func TestExtractContentValue(t *testing.T) {
alice := test.NewUser(t)
room := test.NewRoom(t, alice)
tests := []struct {
name string
event *types.HeaderedEvent
want string
}{
{
name: "returns creator ID for create events",
event: room.Events()[0],
want: alice.ID,
},
{
name: "returns the alias for canonical alias events",
event: room.CreateEvent(t, alice, spec.MRoomCanonicalAlias, map[string]string{"alias": "#test:test"}),
want: "#test:test",
},
{
name: "returns the history_visibility for history visibility events",
event: room.CreateEvent(t, alice, spec.MRoomHistoryVisibility, map[string]string{"history_visibility": "shared"}),
want: "shared",
},
{
name: "returns the join rules for join_rules events",
event: room.CreateEvent(t, alice, spec.MRoomJoinRules, map[string]string{"join_rule": "public"}),
want: "public",
},
{
name: "returns the membership for room_member events",
event: room.CreateEvent(t, alice, spec.MRoomMember, map[string]string{"membership": "join"}, test.WithStateKey(alice.ID)),
want: "join",
},
{
name: "returns the room name for room_name events",
event: room.CreateEvent(t, alice, spec.MRoomName, map[string]string{"name": "testing"}, test.WithStateKey(alice.ID)),
want: "testing",
},
{
name: "returns the room avatar for avatar events",
event: room.CreateEvent(t, alice, spec.MRoomAvatar, map[string]string{"url": "mxc://testing"}, test.WithStateKey(alice.ID)),
want: "mxc://testing",
},
{
name: "returns the room topic for topic events",
event: room.CreateEvent(t, alice, spec.MRoomTopic, map[string]string{"topic": "testing"}, test.WithStateKey(alice.ID)),
want: "testing",
},
{
name: "returns guest_access for guest access events",
event: room.CreateEvent(t, alice, "m.room.guest_access", map[string]string{"guest_access": "forbidden"}, test.WithStateKey(alice.ID)),
want: "forbidden",
},
{
name: "returns empty string if key can't be found or unknown event",
event: room.CreateEvent(t, alice, "idontexist", nil),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, ExtractContentValue(tt.event), "ExtractContentValue(%v)", tt.event)
})
}
}

View file

@ -114,6 +114,11 @@ func (c *Global) Verify(configErrs *ConfigErrors) {
checkNotEmpty(configErrs, "global.server_name", string(c.ServerName)) checkNotEmpty(configErrs, "global.server_name", string(c.ServerName))
checkNotEmpty(configErrs, "global.private_key", string(c.PrivateKeyPath)) checkNotEmpty(configErrs, "global.private_key", string(c.PrivateKeyPath))
// Check that client well-known has a proper format
if c.WellKnownClientName != "" && !strings.HasPrefix(c.WellKnownClientName, "http://") && !strings.HasPrefix(c.WellKnownClientName, "https://") {
configErrs.Add("The configuration for well_known_client_name does not have a proper format, consider adding http:// or https://. Some clients may fail to connect.")
}
for _, v := range c.VirtualHosts { for _, v := range c.VirtualHosts {
v.Verify(configErrs) v.Verify(configErrs)
} }

View file

@ -54,7 +54,7 @@ global:
key_id: ed25519:auto key_id: ed25519:auto
key_validity_period: 168h0m0s key_validity_period: 168h0m0s
well_known_server_name: "localhost:443" well_known_server_name: "localhost:443"
well_known_client_name: "localhost:443" well_known_client_name: "https://localhost"
trusted_third_party_id_servers: trusted_third_party_id_servers:
- matrix.org - matrix.org
- vector.im - vector.im

View file

@ -21,6 +21,10 @@ type UserAPI struct {
// Users who register on this homeserver will automatically // Users who register on this homeserver will automatically
// be joined to the rooms listed under this option. // be joined to the rooms listed under this option.
AutoJoinRooms []string `yaml:"auto_join_rooms"` AutoJoinRooms []string `yaml:"auto_join_rooms"`
// The number of workers to start for the DeviceListUpdater. Defaults to 8.
// This only needs updating if the "InputDeviceListUpdate" stream keeps growing indefinitely.
WorkerCount int `yaml:"worker_count"`
} }
const DefaultOpenIDTokenLifetimeMS = 3600000 // 60 minutes const DefaultOpenIDTokenLifetimeMS = 3600000 // 60 minutes
@ -28,6 +32,7 @@ const DefaultOpenIDTokenLifetimeMS = 3600000 // 60 minutes
func (c *UserAPI) Defaults(opts DefaultOpts) { func (c *UserAPI) Defaults(opts DefaultOpts) {
c.BCryptCost = bcrypt.DefaultCost c.BCryptCost = bcrypt.DefaultCost
c.OpenIDTokenLifetimeMS = DefaultOpenIDTokenLifetimeMS c.OpenIDTokenLifetimeMS = DefaultOpenIDTokenLifetimeMS
c.WorkerCount = 8
if opts.Generate { if opts.Generate {
if !opts.SingleDatabase { if !opts.SingleDatabase {
c.AccountDatabase.ConnectionString = "file:userapi_accounts.db" c.AccountDatabase.ConnectionString = "file:userapi_accounts.db"

View file

@ -8,6 +8,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/getsentry/sentry-go"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/setup/config"
@ -38,7 +39,7 @@ func (s *NATSInstance) Prepare(process *process.ProcessContext, cfg *config.JetS
defer natsLock.Unlock() defer natsLock.Unlock()
// check if we need an in-process NATS Server // check if we need an in-process NATS Server
if len(cfg.Addresses) != 0 { if len(cfg.Addresses) != 0 {
return setupNATS(cfg, nil) return setupNATS(process, cfg, nil)
} }
if s.Server == nil { if s.Server == nil {
var err error var err error
@ -70,7 +71,7 @@ func (s *NATSInstance) Prepare(process *process.ProcessContext, cfg *config.JetS
process.ComponentFinished() process.ComponentFinished()
}() }()
} }
if !s.ReadyForConnections(time.Second * 10) { if !s.ReadyForConnections(time.Second * 60) {
logrus.Fatalln("NATS did not start in time") logrus.Fatalln("NATS did not start in time")
} }
// reuse existing connections // reuse existing connections
@ -81,13 +82,14 @@ func (s *NATSInstance) Prepare(process *process.ProcessContext, cfg *config.JetS
if err != nil { if err != nil {
logrus.Fatalln("Failed to create NATS client") logrus.Fatalln("Failed to create NATS client")
} }
js, _ := setupNATS(cfg, nc) js, _ := setupNATS(process, cfg, nc)
s.js = js s.js = js
s.nc = nc s.nc = nc
return js, nc return js, nc
} }
func setupNATS(cfg *config.JetStream, nc *natsclient.Conn) (natsclient.JetStreamContext, *natsclient.Conn) { // nolint:gocyclo
func setupNATS(process *process.ProcessContext, cfg *config.JetStream, nc *natsclient.Conn) (natsclient.JetStreamContext, *natsclient.Conn) {
var s nats.JetStreamContext var s nats.JetStreamContext
var err error var err error
if nc == nil { if nc == nil {
@ -131,9 +133,102 @@ func setupNATS(cfg *config.JetStream, nc *natsclient.Conn) (natsclient.JetStream
} }
for _, stream := range streams { // streams are defined in streams.go for _, stream := range streams { // streams are defined in streams.go
err = configureStream(stream, cfg, s) name := cfg.Prefixed(stream.Name)
if err != nil { info, err := s.StreamInfo(name)
logrus.WithError(err).WithField("stream", stream.Name).Fatal("unable to configure a stream") if err != nil && err != natsclient.ErrStreamNotFound {
logrus.WithError(err).Fatal("Unable to get stream info")
}
subjects := stream.Subjects
if len(subjects) == 0 {
// By default we want each stream to listen for the subjects
// that are either an exact match for the stream name, or where
// the first part of the subject is the stream name. ">" is a
// wildcard in NATS for one or more subject tokens. In the case
// that the stream is called "Foo", this will match any message
// with the subject "Foo", "Foo.Bar" or "Foo.Bar.Baz" etc.
subjects = []string{name, name + ".>"}
}
if info != nil {
// If the stream config doesn't match what we expect, try to update
// it. If that doesn't work then try to blow it away and we'll then
// recreate it in the next section.
// Each specific option that we set must be checked by hand, as if
// you DeepEqual the whole config struct, it will always show that
// there's a difference because the NATS Server will return defaults
// in the stream info.
switch {
case !reflect.DeepEqual(info.Config.Subjects, subjects):
fallthrough
case info.Config.Retention != stream.Retention:
fallthrough
case info.Config.Storage != stream.Storage:
fallthrough
case info.Config.MaxAge != stream.MaxAge:
// Try updating the stream first, as many things can be updated
// non-destructively.
if info, err = s.UpdateStream(stream); err != nil {
logrus.WithError(err).Warnf("Unable to update stream %q, recreating...", name)
// We failed to update the stream, this is a last attempt to get
// things working but may result in data loss.
if err = s.DeleteStream(name); err != nil {
logrus.WithError(err).Fatalf("Unable to delete stream %q", name)
}
info = nil
}
}
}
if info == nil {
// If we're trying to keep everything in memory (e.g. unit tests)
// then overwrite the storage policy.
if cfg.InMemory {
stream.Storage = natsclient.MemoryStorage
}
// Namespace the streams without modifying the original streams
// array, otherwise we end up with namespaces on namespaces.
namespaced := *stream
namespaced.Name = name
namespaced.Subjects = subjects
if _, err = s.AddStream(&namespaced); err != nil {
logger := logrus.WithError(err).WithFields(logrus.Fields{
"stream": namespaced.Name,
"subjects": namespaced.Subjects,
})
// If the stream was supposed to be in-memory to begin with
// then an error here is fatal so we'll give up.
if namespaced.Storage == natsclient.MemoryStorage {
logger.WithError(err).Fatal("Unable to add in-memory stream")
}
// The stream was supposed to be on disk. Let's try starting
// Dendrite with the stream in-memory instead. That'll mean that
// we can't recover anything that was queued on the disk but we
// will still be able to start and run hopefully in the meantime.
logger.WithError(err).Error("Unable to add stream")
sentry.CaptureException(fmt.Errorf("Unable to add stream %q: %w", namespaced.Name, err))
namespaced.Storage = natsclient.MemoryStorage
if _, err = s.AddStream(&namespaced); err != nil {
// We tried to add the stream in-memory instead but something
// went wrong. That's an unrecoverable situation so we will
// give up at this point.
logger.WithError(err).Fatal("Unable to add in-memory stream")
}
if stream.Storage != namespaced.Storage {
// We've managed to add the stream in memory. What's on the
// disk will be left alone, but our ability to recover from a
// future crash will be limited. Yell about it.
err := fmt.Errorf("Stream %q is running in-memory; this may be due to data corruption in the JetStream storage directory", namespaced.Name)
sentry.CaptureException(err)
process.Degraded(err)
}
}
// Kozi's code, which defers from the original dendrite code
// err = configureStream(stream, cfg, s)
// if err != nil {
// logrus.WithError(err).WithField("stream", stream.Name).Fatal("unable to configure a stream")
} }
} }

View file

@ -20,6 +20,7 @@ var (
InputDeviceListUpdate = "InputDeviceListUpdate" InputDeviceListUpdate = "InputDeviceListUpdate"
InputSigningKeyUpdate = "InputSigningKeyUpdate" InputSigningKeyUpdate = "InputSigningKeyUpdate"
OutputRoomEvent = "OutputRoomEvent" OutputRoomEvent = "OutputRoomEvent"
OutputAppserviceEvent = "OutputAppserviceEvent"
OutputSendToDeviceEvent = "OutputSendToDeviceEvent" OutputSendToDeviceEvent = "OutputSendToDeviceEvent"
OutputKeyChangeEvent = "OutputKeyChangeEvent" OutputKeyChangeEvent = "OutputKeyChangeEvent"
OutputTypingEvent = "OutputTypingEvent" OutputTypingEvent = "OutputTypingEvent"
@ -66,6 +67,11 @@ var streams = []*nats.StreamConfig{
Retention: nats.InterestPolicy, Retention: nats.InterestPolicy,
Storage: nats.FileStorage, Storage: nats.FileStorage,
}, },
{
Name: OutputAppserviceEvent,
Retention: nats.InterestPolicy,
Storage: nats.FileStorage,
},
{ {
Name: OutputSendToDeviceEvent, Name: OutputSendToDeviceEvent,
Retention: nats.InterestPolicy, Retention: nats.InterestPolicy,

View file

@ -301,7 +301,7 @@ func (p *DB) ChildrenForParent(ctx context.Context, eventID, relType string, rec
} }
children = append(children, evInfo) children = append(children, evInfo)
} }
return children, nil return children, rows.Err()
} }
func (p *DB) ParentForChild(ctx context.Context, eventID, relType string) (*eventInfo, error) { func (p *DB) ParentForChild(ctx context.Context, eventID, relType string) (*eventInfo, error) {

View file

@ -31,6 +31,7 @@ import (
"github.com/matrix-org/dendrite/setup/jetstream" "github.com/matrix-org/dendrite/setup/jetstream"
"github.com/matrix-org/dendrite/setup/process" "github.com/matrix-org/dendrite/setup/process"
"github.com/matrix-org/dendrite/syncapi/notifier" "github.com/matrix-org/dendrite/syncapi/notifier"
"github.com/matrix-org/dendrite/syncapi/producers"
"github.com/matrix-org/dendrite/syncapi/storage" "github.com/matrix-org/dendrite/syncapi/storage"
"github.com/matrix-org/dendrite/syncapi/streams" "github.com/matrix-org/dendrite/syncapi/streams"
"github.com/matrix-org/dendrite/syncapi/synctypes" "github.com/matrix-org/dendrite/syncapi/synctypes"
@ -55,6 +56,7 @@ type OutputRoomEventConsumer struct {
inviteStream streams.StreamProvider inviteStream streams.StreamProvider
notifier *notifier.Notifier notifier *notifier.Notifier
fts fulltext.Indexer fts fulltext.Indexer
asProducer *producers.AppserviceEventProducer
} }
// NewOutputRoomEventConsumer creates a new OutputRoomEventConsumer. Call Start() to begin consuming from room servers. // NewOutputRoomEventConsumer creates a new OutputRoomEventConsumer. Call Start() to begin consuming from room servers.
@ -68,6 +70,7 @@ func NewOutputRoomEventConsumer(
inviteStream streams.StreamProvider, inviteStream streams.StreamProvider,
rsAPI api.SyncRoomserverAPI, rsAPI api.SyncRoomserverAPI,
fts *fulltext.Search, fts *fulltext.Search,
asProducer *producers.AppserviceEventProducer,
) *OutputRoomEventConsumer { ) *OutputRoomEventConsumer {
return &OutputRoomEventConsumer{ return &OutputRoomEventConsumer{
ctx: process.Context(), ctx: process.Context(),
@ -81,6 +84,7 @@ func NewOutputRoomEventConsumer(
inviteStream: inviteStream, inviteStream: inviteStream,
rsAPI: rsAPI, rsAPI: rsAPI,
fts: fts, fts: fts,
asProducer: asProducer,
} }
} }
@ -119,6 +123,11 @@ func (s *OutputRoomEventConsumer) onMessage(ctx context.Context, msgs []*nats.Ms
} }
} }
err = s.onNewRoomEvent(s.ctx, *output.NewRoomEvent) err = s.onNewRoomEvent(s.ctx, *output.NewRoomEvent)
if err == nil && s.asProducer != nil {
if err = s.asProducer.ProduceRoomEvents(msg); err != nil {
log.WithError(err).Warn("failed to produce OutputAppserviceEvent")
}
}
case api.OutputTypeOldRoomEvent: case api.OutputTypeOldRoomEvent:
err = s.onOldRoomEvent(s.ctx, *output.OldRoomEvent) err = s.onOldRoomEvent(s.ctx, *output.OldRoomEvent)
case api.OutputTypeNewInviteEvent: case api.OutputTypeNewInviteEvent:

View file

@ -0,0 +1,33 @@
// Copyright 2023 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 (
"github.com/nats-io/nats.go"
)
// AppserviceEventProducer produces events for the appservice API to consume
type AppserviceEventProducer struct {
Topic string
JetStream nats.JetStreamContext
}
func (a *AppserviceEventProducer) ProduceRoomEvents(
msg *nats.Msg,
) error {
msg.Subject = a.Topic
_, err := a.JetStream.PublishMsg(msg)
return err
}

View file

@ -44,7 +44,7 @@ func GetEvent(
rsAPI api.SyncRoomserverAPI, rsAPI api.SyncRoomserverAPI,
) util.JSONResponse { ) util.JSONResponse {
ctx := req.Context() ctx := req.Context()
db, err := syncDB.NewDatabaseTransaction(ctx) db, err := syncDB.NewDatabaseSnapshot(ctx)
logger := util.GetLogger(ctx).WithFields(logrus.Fields{ logger := util.GetLogger(ctx).WithFields(logrus.Fields{
"event_id": eventID, "event_id": eventID,
"room_id": rawRoomID, "room_id": rawRoomID,
@ -56,6 +56,7 @@ func GetEvent(
JSON: spec.InternalServerError{}, JSON: spec.InternalServerError{},
} }
} }
defer db.Rollback() // nolint: errcheck
roomID, err := spec.NewRoomID(rawRoomID) roomID, err := spec.NewRoomID(rawRoomID)
if err != nil { if err != nil {

View file

@ -392,7 +392,7 @@ func currentRoomStateRowsToStreamEvents(rows *sql.Rows) ([]types.StreamEvent, er
}) })
} }
return events, nil return events, rows.Err()
} }
func rowsToEvents(rows *sql.Rows) ([]*rstypes.HeaderedEvent, error) { func rowsToEvents(rows *sql.Rows) ([]*rstypes.HeaderedEvent, error) {

View file

@ -19,6 +19,7 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/matrix-org/dendrite/internal"
"github.com/matrix-org/dendrite/internal/sqlutil" "github.com/matrix-org/dendrite/internal/sqlutil"
rstypes "github.com/matrix-org/dendrite/roomserver/types" rstypes "github.com/matrix-org/dendrite/roomserver/types"
"github.com/matrix-org/dendrite/syncapi/storage/tables" "github.com/matrix-org/dendrite/syncapi/storage/tables"
@ -160,6 +161,7 @@ func (s *membershipsStatements) SelectMemberships(
if err != nil { if err != nil {
return return
} }
defer internal.CloseAndLogIfError(ctx, rows, "SelectMemberships: failed to close rows")
var ( var (
eventID string eventID string
) )

View file

@ -164,7 +164,7 @@ func (s *peekStatements) SelectPeekingDevices(
devices = append(devices, types.PeekingDevice{UserID: userID, DeviceID: deviceID}) devices = append(devices, types.PeekingDevice{UserID: userID, DeviceID: deviceID})
result[roomID] = devices result[roomID] = devices
} }
return result, nil return result, rows.Err()
} }
func (s *peekStatements) SelectMaxPeekID( func (s *peekStatements) SelectMaxPeekID(

View file

@ -162,7 +162,7 @@ func (p *presenceStatements) GetPresenceForUsers(
presence.ClientFields.Presence = presence.Presence.String() presence.ClientFields.Presence = presence.Presence.String()
result = append(result, presence) result = append(result, presence)
} }
return result, err return result, rows.Err()
} }
func (p *presenceStatements) GetMaxPresenceID(ctx context.Context, txn *sql.Tx) (pos types.StreamPosition, err error) { func (p *presenceStatements) GetMaxPresenceID(ctx context.Context, txn *sql.Tx) (pos types.StreamPosition, err error) {

View file

@ -177,7 +177,7 @@ func (s *currentRoomStateStatements) SelectJoinedUsers(
users = append(users, userID) users = append(users, userID)
result[roomID] = users result[roomID] = users
} }
return result, nil return result, rows.Err()
} }
// SelectJoinedUsersInRoom returns a map of room ID to a list of joined user IDs for a given room. // SelectJoinedUsersInRoom returns a map of room ID to a list of joined user IDs for a given room.
@ -236,7 +236,7 @@ func (s *currentRoomStateStatements) SelectRoomIDsWithMembership(
} }
result = append(result, roomID) result = append(result, roomID)
} }
return result, nil return result, rows.Err()
} }
// SelectRoomIDsWithAnyMembership returns a map of all memberships for the given user. // SelectRoomIDsWithAnyMembership returns a map of all memberships for the given user.
@ -419,7 +419,7 @@ func currentRoomStateRowsToStreamEvents(rows *sql.Rows) ([]types.StreamEvent, er
}) })
} }
return events, nil return events, rows.Err()
} }
func rowsToEvents(rows *sql.Rows) ([]*rstypes.HeaderedEvent, error) { func rowsToEvents(rows *sql.Rows) ([]*rstypes.HeaderedEvent, error) {

View file

@ -176,7 +176,7 @@ func (s *inviteEventsStatements) SelectInviteEventsInRange(
if lastPos == 0 { if lastPos == 0 {
lastPos = r.To lastPos = r.To
} }
return result, retired, lastPos, nil return result, retired, lastPos, rows.Err()
} }
func (s *inviteEventsStatements) SelectMaxInviteID( func (s *inviteEventsStatements) SelectMaxInviteID(

Some files were not shown because too many files have changed in this diff Show more