Merge branch 'main' into neilalexander/downloadstate
This commit is contained in:
commit
391d531ec3
7
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
7
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
|
@ -7,6 +7,11 @@ about: Create a report to help us improve
|
||||||
<!--
|
<!--
|
||||||
All bug reports must provide the following background information
|
All bug reports must provide the following background information
|
||||||
Text between <!-- and --> marks will be invisible in the report.
|
Text between <!-- and --> marks will be invisible in the report.
|
||||||
|
|
||||||
|
IF YOUR ISSUE IS CONSIDERED A SECURITY VULNERABILITY THEN PLEASE STOP
|
||||||
|
AND DO NOT POST IT AS A GITHUB ISSUE! Please report the issue responsibly by
|
||||||
|
disclosing in private by email to security@matrix.org instead. For more details, please
|
||||||
|
see: https://www.matrix.org/security-disclosure-policy/
|
||||||
-->
|
-->
|
||||||
|
|
||||||
### Background information
|
### Background information
|
||||||
|
@ -18,7 +23,6 @@ Text between <!-- and --> marks will be invisible in the report.
|
||||||
- **`go version`**:
|
- **`go version`**:
|
||||||
- **Client used (if applicable)**:
|
- **Client used (if applicable)**:
|
||||||
|
|
||||||
|
|
||||||
### Description
|
### Description
|
||||||
|
|
||||||
- **What** is the problem:
|
- **What** is the problem:
|
||||||
|
@ -38,7 +42,6 @@ Examples of good descriptions:
|
||||||
- How: "Lots of logs about device change updates"
|
- How: "Lots of logs about device change updates"
|
||||||
- When: "After my server joined Matrix HQ"
|
- When: "After my server joined Matrix HQ"
|
||||||
|
|
||||||
|
|
||||||
Examples of bad descriptions:
|
Examples of bad descriptions:
|
||||||
- What: "Can't send messages" - This is bad because it isn't specfic enough. Which endpoint isn't working and what is the response code? Does the message send but encryption fail?
|
- What: "Can't send messages" - This is bad because it isn't specfic enough. Which endpoint isn't working and what is the response code? Does the message send but encryption fail?
|
||||||
- Who: "Me" - Who are you? Running the server or a user on a Dendrite server?
|
- Who: "Me" - Who are you? Running the server or a user on a Dendrite server?
|
||||||
|
|
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -1,8 +1,8 @@
|
||||||
### Pull Request Checklist
|
### Pull Request Checklist
|
||||||
|
|
||||||
<!-- Please read docs/CONTRIBUTING.md before submitting your pull request -->
|
<!-- Please read https://matrix-org.github.io/dendrite/development/contributing before submitting your pull request -->
|
||||||
|
|
||||||
* [ ] I have added tests for PR _or_ I have justified why this PR doesn't need tests.
|
* [ ] I have added tests for PR _or_ I have justified why this PR doesn't need tests.
|
||||||
* [ ] Pull request includes a [sign off](https://github.com/matrix-org/dendrite/blob/main/docs/CONTRIBUTING.md#sign-off)
|
* [ ] Pull request includes a [sign off below using a legally identifiable name](https://matrix-org.github.io/dendrite/development/contributing#sign-off) _or_ I have already signed off privately
|
||||||
|
|
||||||
Signed-off-by: `Your Name <your@email.example.org>`
|
Signed-off-by: `Your Name <your@email.example.org>`
|
||||||
|
|
22
CHANGES.md
22
CHANGES.md
|
@ -1,5 +1,27 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Dendrite 0.10.2 (2022-10-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Dendrite will now fail to start if there is an obvious problem with the configured `max_open_conns` when using PostgreSQL database backends, since this can lead to instability and performance issues
|
||||||
|
* More information on this is available [in the documentation](https://matrix-org.github.io/dendrite/installation/start/optimisation#postgresql-connection-limit)
|
||||||
|
* Unnecessary/empty fields will no longer be sent in `/sync` responses
|
||||||
|
* It is now possible to configure `old_private_keys` from previous Matrix installations on the same domain if only public key is known, to make it easier to expire old keys correctly
|
||||||
|
* You can configure either just the `private_key` path, or you can supply both the `public_key` and `key_id`
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* The sync transaction behaviour has been modified further so that errors in one stream should not propagate to other streams unnecessarily
|
||||||
|
* Rooms should now be classified as DM rooms correctly by passing through `is_direct` and unsigned hints
|
||||||
|
* A bug which caused marking device lists as stale to consume lots of CPU has been fixed
|
||||||
|
* Users accepting invites should no longer cause unnecessary federated joins if there are already other local users in the room
|
||||||
|
* The sync API state range queries have been optimised by adding missing indexes
|
||||||
|
* It should now be possible to configure non-English languages for full-text search in `search.language`
|
||||||
|
* The roomserver will no longer attempt to perform federated requests to the local server when trying to fetch missing events
|
||||||
|
* The `/keys/upload` endpoint will now always return the `one_time_keys_counts`, which may help with E2EE reliability
|
||||||
|
* The sync API will now retrieve the latest stream position before processing each stream rather than at the beginning of the request, to hopefully reduce the number of round-trips to `/sync`
|
||||||
|
|
||||||
## Dendrite 0.10.1 (2022-09-30)
|
## Dendrite 0.10.1 (2022-09-30)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
|
@ -79,7 +79,7 @@ $ ./bin/dendrite-monolith-server --tls-cert server.crt --tls-key server.key --co
|
||||||
|
|
||||||
# Create an user account (add -admin for an admin user).
|
# Create an user account (add -admin for an admin user).
|
||||||
# Specify the localpart only, e.g. 'alice' for '@alice:domain.com'
|
# Specify the localpart only, e.g. 'alice' for '@alice:domain.com'
|
||||||
$ ./bin/create-account --config dendrite.yaml --url http://localhost:8008 --username alice
|
$ ./bin/create-account --config dendrite.yaml --username alice
|
||||||
```
|
```
|
||||||
|
|
||||||
Then point your favourite Matrix client at `http://localhost:8008` or `https://localhost:8448`.
|
Then point your favourite Matrix client at `http://localhost:8008` or `https://localhost:8448`.
|
||||||
|
@ -90,7 +90,7 @@ We use a script called Are We Synapse Yet which checks Sytest compliance rates.
|
||||||
test rig with around 900 tests. The script works out how many of these tests are passing on Dendrite and it
|
test rig with around 900 tests. The script works out how many of these tests are passing on Dendrite and it
|
||||||
updates with CI. As of August 2022 we're at around 90% CS API coverage and 95% Federation coverage, though check
|
updates with CI. As of August 2022 we're at around 90% CS API coverage and 95% Federation coverage, though check
|
||||||
CI for the latest numbers. In practice, this means you can communicate locally and via federation with Synapse
|
CI for the latest numbers. In practice, this means you can communicate locally and via federation with Synapse
|
||||||
servers such as matrix.org reasonably well, although there are still some missing features (like Search).
|
servers such as matrix.org reasonably well, although there are still some missing features (like SSO and Third-party ID APIs).
|
||||||
|
|
||||||
We are prioritising features that will benefit single-user homeservers first (e.g Receipts, E2E) rather
|
We are prioritising features that will benefit single-user homeservers first (e.g Receipts, E2E) rather
|
||||||
than features that massive deployments may be interested in (OpenID, Guests, Admin APIs, AS API).
|
than features that massive deployments may be interested in (OpenID, Guests, Admin APIs, AS API).
|
||||||
|
@ -112,6 +112,7 @@ This means Dendrite supports amongst others:
|
||||||
- Guests
|
- Guests
|
||||||
- User Directory
|
- User Directory
|
||||||
- Presence
|
- Presence
|
||||||
|
- Fulltext search
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,12 @@ func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login,
|
||||||
JSON: jsonerror.BadJSON("A username must be supplied."),
|
JSON: jsonerror.BadJSON("A username must be supplied."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(r.Password) == 0 {
|
||||||
|
return nil, &util.JSONResponse{
|
||||||
|
Code: http.StatusUnauthorized,
|
||||||
|
JSON: jsonerror.BadJSON("A password must be supplied."),
|
||||||
|
}
|
||||||
|
}
|
||||||
localpart, err := userutil.ParseUsernameParam(username, &t.Config.Matrix.ServerName)
|
localpart, err := userutil.ParseUsernameParam(username, &t.Config.Matrix.ServerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
|
|
|
@ -1,138 +0,0 @@
|
||||||
// Copyright 2019 Alex Chen
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package routing
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
"github.com/matrix-org/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
type getEventRequest struct {
|
|
||||||
req *http.Request
|
|
||||||
device *userapi.Device
|
|
||||||
roomID string
|
|
||||||
eventID string
|
|
||||||
cfg *config.ClientAPI
|
|
||||||
requestedEvent *gomatrixserverlib.Event
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetEvent implements GET /_matrix/client/r0/rooms/{roomId}/event/{eventId}
|
|
||||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-rooms-roomid-event-eventid
|
|
||||||
func GetEvent(
|
|
||||||
req *http.Request,
|
|
||||||
device *userapi.Device,
|
|
||||||
roomID string,
|
|
||||||
eventID string,
|
|
||||||
cfg *config.ClientAPI,
|
|
||||||
rsAPI api.ClientRoomserverAPI,
|
|
||||||
) util.JSONResponse {
|
|
||||||
eventsReq := api.QueryEventsByIDRequest{
|
|
||||||
EventIDs: []string{eventID},
|
|
||||||
}
|
|
||||||
var eventsResp api.QueryEventsByIDResponse
|
|
||||||
err := rsAPI.QueryEventsByID(req.Context(), &eventsReq, &eventsResp)
|
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("queryAPI.QueryEventsByID failed")
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(eventsResp.Events) == 0 {
|
|
||||||
// Event not found locally
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusNotFound,
|
|
||||||
JSON: jsonerror.NotFound("The event was not found or you do not have permission to read this event"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
requestedEvent := eventsResp.Events[0].Event
|
|
||||||
|
|
||||||
r := getEventRequest{
|
|
||||||
req: req,
|
|
||||||
device: device,
|
|
||||||
roomID: roomID,
|
|
||||||
eventID: eventID,
|
|
||||||
cfg: cfg,
|
|
||||||
requestedEvent: requestedEvent,
|
|
||||||
}
|
|
||||||
|
|
||||||
stateReq := api.QueryStateAfterEventsRequest{
|
|
||||||
RoomID: r.requestedEvent.RoomID(),
|
|
||||||
PrevEventIDs: r.requestedEvent.PrevEventIDs(),
|
|
||||||
StateToFetch: []gomatrixserverlib.StateKeyTuple{{
|
|
||||||
EventType: gomatrixserverlib.MRoomMember,
|
|
||||||
StateKey: device.UserID,
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
var stateResp api.QueryStateAfterEventsResponse
|
|
||||||
if err := rsAPI.QueryStateAfterEvents(req.Context(), &stateReq, &stateResp); err != nil {
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("queryAPI.QueryStateAfterEvents failed")
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !stateResp.RoomExists {
|
|
||||||
util.GetLogger(req.Context()).Errorf("Expected to find room for event %s but failed", r.requestedEvent.EventID())
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !stateResp.PrevEventsExist {
|
|
||||||
// Missing some events locally; stateResp.StateEvents unavailable.
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusNotFound,
|
|
||||||
JSON: jsonerror.NotFound("The event was not found or you do not have permission to read this event"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var appService *config.ApplicationService
|
|
||||||
if device.AppserviceID != "" {
|
|
||||||
for _, as := range cfg.Derived.ApplicationServices {
|
|
||||||
if as.ID == device.AppserviceID {
|
|
||||||
appService = &as
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, stateEvent := range stateResp.StateEvents {
|
|
||||||
if appService != nil {
|
|
||||||
if !appService.IsInterestedInUserID(*stateEvent.StateKey()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else if !stateEvent.StateKeyEquals(device.UserID) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
membership, err := stateEvent.Membership()
|
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("stateEvent.Membership failed")
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
if membership == gomatrixserverlib.Join {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusOK,
|
|
||||||
JSON: gomatrixserverlib.ToClientEvent(r.requestedEvent, gomatrixserverlib.FormatAll),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusNotFound,
|
|
||||||
JSON: jsonerror.NotFound("The event was not found or you do not have permission to read this event"),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -19,11 +19,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/keyserver/api"
|
"github.com/matrix-org/dendrite/keyserver/api"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type uploadKeysRequest struct {
|
type uploadKeysRequest struct {
|
||||||
|
@ -77,7 +78,6 @@ func UploadKeys(req *http.Request, keyAPI api.ClientKeyAPI, device *userapi.Devi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
keyCount := make(map[string]int)
|
keyCount := make(map[string]int)
|
||||||
// we only return key counts when the client uploads OTKs
|
|
||||||
if len(uploadRes.OneTimeKeyCounts) > 0 {
|
if len(uploadRes.OneTimeKeyCounts) > 0 {
|
||||||
keyCount = uploadRes.OneTimeKeyCounts[0].KeyCount
|
keyCount = uploadRes.OneTimeKeyCounts[0].KeyCount
|
||||||
}
|
}
|
||||||
|
|
|
@ -373,15 +373,6 @@ func Setup(
|
||||||
nil, cfg, rsAPI, transactionsCache)
|
nil, cfg, rsAPI, transactionsCache)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPut, http.MethodOptions)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
v3mux.Handle("/rooms/{roomID}/event/{eventID}",
|
|
||||||
httputil.MakeAuthAPI("rooms_get_event", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
|
||||||
if err != nil {
|
|
||||||
return util.ErrorResponse(err)
|
|
||||||
}
|
|
||||||
return GetEvent(req, device, vars["roomID"], vars["eventID"], cfg, rsAPI)
|
|
||||||
}),
|
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
|
||||||
|
|
||||||
v3mux.Handle("/rooms/{roomID}/state", httputil.MakeAuthAPI("room_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
v3mux.Handle("/rooms/{roomID}/state", httputil.MakeAuthAPI("room_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
|
@ -18,12 +18,17 @@ global:
|
||||||
private_key: matrix_key.pem
|
private_key: matrix_key.pem
|
||||||
|
|
||||||
# The paths and expiry timestamps (as a UNIX timestamp in millisecond precision)
|
# The paths and expiry timestamps (as a UNIX timestamp in millisecond precision)
|
||||||
# to old signing private keys that were formerly in use on this domain. These
|
# to old signing keys that were formerly in use on this domain name. These
|
||||||
# keys will not be used for federation request or event signing, but will be
|
# keys will not be used for federation request or event signing, but will be
|
||||||
# provided to any other homeserver that asks when trying to verify old events.
|
# provided to any other homeserver that asks when trying to verify old events.
|
||||||
old_private_keys:
|
old_private_keys:
|
||||||
|
# If the old private key file is available:
|
||||||
# - private_key: old_matrix_key.pem
|
# - private_key: old_matrix_key.pem
|
||||||
# expired_at: 1601024554498
|
# expired_at: 1601024554498
|
||||||
|
# If only the public key (in base64 format) and key ID are known:
|
||||||
|
# - public_key: mn59Kxfdq9VziYHSBzI7+EDPDcBS2Xl7jeUdiiQcOnM=
|
||||||
|
# key_id: ed25519:mykeyid
|
||||||
|
# expired_at: 1601024554498
|
||||||
|
|
||||||
# How long a remote server can cache our server signing key before requesting it
|
# How long a remote server can cache our server signing key before requesting it
|
||||||
# again. Increasing this number will reduce the number of requests made by other
|
# again. Increasing this number will reduce the number of requests made by other
|
||||||
|
|
|
@ -18,12 +18,17 @@ global:
|
||||||
private_key: matrix_key.pem
|
private_key: matrix_key.pem
|
||||||
|
|
||||||
# The paths and expiry timestamps (as a UNIX timestamp in millisecond precision)
|
# The paths and expiry timestamps (as a UNIX timestamp in millisecond precision)
|
||||||
# to old signing private keys that were formerly in use on this domain. These
|
# to old signing keys that were formerly in use on this domain name. These
|
||||||
# keys will not be used for federation request or event signing, but will be
|
# keys will not be used for federation request or event signing, but will be
|
||||||
# provided to any other homeserver that asks when trying to verify old events.
|
# provided to any other homeserver that asks when trying to verify old events.
|
||||||
old_private_keys:
|
old_private_keys:
|
||||||
|
# If the old private key file is available:
|
||||||
# - private_key: old_matrix_key.pem
|
# - private_key: old_matrix_key.pem
|
||||||
# expired_at: 1601024554498
|
# expired_at: 1601024554498
|
||||||
|
# If only the public key (in base64 format) and key ID are known:
|
||||||
|
# - public_key: mn59Kxfdq9VziYHSBzI7+EDPDcBS2Xl7jeUdiiQcOnM=
|
||||||
|
# key_id: ed25519:mykeyid
|
||||||
|
# expired_at: 1601024554498
|
||||||
|
|
||||||
# How long a remote server can cache our server signing key before requesting it
|
# How long a remote server can cache our server signing key before requesting it
|
||||||
# again. Increasing this number will reduce the number of requests made by other
|
# again. Increasing this number will reduce the number of requests made by other
|
||||||
|
|
|
@ -55,7 +55,7 @@ matrix.example.com {
|
||||||
# Change the end of each reverse_proxy line to the correct
|
# Change the end of each reverse_proxy line to the correct
|
||||||
# address for your various services.
|
# address for your various services.
|
||||||
@sync_api {
|
@sync_api {
|
||||||
path_regexp /_matrix/client/.*?/(sync|user/.*?/filter/?.*|keys/changes|rooms/.*?/messages)$
|
path_regexp /_matrix/client/.*?/(sync|user/.*?/filter/?.*|keys/changes|rooms/.*?/(messages|context/.*?|event/.*?))$
|
||||||
}
|
}
|
||||||
reverse_proxy @sync_api sync_api:8073
|
reverse_proxy @sync_api sync_api:8073
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,10 @@ VirtualHost {
|
||||||
# /_matrix/client/.*/user/{userId}/filter/{filterID}
|
# /_matrix/client/.*/user/{userId}/filter/{filterID}
|
||||||
# /_matrix/client/.*/keys/changes
|
# /_matrix/client/.*/keys/changes
|
||||||
# /_matrix/client/.*/rooms/{roomId}/messages
|
# /_matrix/client/.*/rooms/{roomId}/messages
|
||||||
|
# /_matrix/client/.*/rooms/{roomId}/context/{eventID}
|
||||||
|
# /_matrix/client/.*/rooms/{roomId}/event/{eventID}
|
||||||
# to sync_api
|
# to sync_api
|
||||||
ReverseProxy = /_matrix/client/.*?/(sync|user/.*?/filter/?.*|keys/changes|rooms/.*?/messages) http://localhost:8073 600
|
ReverseProxy = /_matrix/client/.*?/(sync|user/.*?/filter/?.*|keys/changes|rooms/.*?/(messages|context/.*?|event/.*?))$ http://localhost:8073 600
|
||||||
ReverseProxy = /_matrix/client http://localhost:8071 600
|
ReverseProxy = /_matrix/client http://localhost:8071 600
|
||||||
ReverseProxy = /_matrix/federation http://localhost:8072 600
|
ReverseProxy = /_matrix/federation http://localhost:8072 600
|
||||||
ReverseProxy = /_matrix/key http://localhost:8072 600
|
ReverseProxy = /_matrix/key http://localhost:8072 600
|
||||||
|
|
|
@ -28,8 +28,10 @@ server {
|
||||||
# /_matrix/client/.*/user/{userId}/filter/{filterID}
|
# /_matrix/client/.*/user/{userId}/filter/{filterID}
|
||||||
# /_matrix/client/.*/keys/changes
|
# /_matrix/client/.*/keys/changes
|
||||||
# /_matrix/client/.*/rooms/{roomId}/messages
|
# /_matrix/client/.*/rooms/{roomId}/messages
|
||||||
|
# /_matrix/client/.*/rooms/{roomId}/context/{eventID}
|
||||||
|
# /_matrix/client/.*/rooms/{roomId}/event/{eventID}
|
||||||
# to sync_api
|
# to sync_api
|
||||||
location ~ /_matrix/client/.*?/(sync|user/.*?/filter/?.*|keys/changes|rooms/.*?/messages)$ {
|
location ~ /_matrix/client/.*?/(sync|user/.*?/filter/?.*|keys/changes|rooms/.*?/(messages|context/.*?|event/.*?))$ {
|
||||||
proxy_pass http://sync_api:8073;
|
proxy_pass http://sync_api:8073;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -159,6 +159,7 @@ type PerformJoinRequest struct {
|
||||||
// The sorted list of servers to try. Servers will be tried sequentially, after de-duplication.
|
// The sorted list of servers to try. Servers will be tried sequentially, after de-duplication.
|
||||||
ServerNames types.ServerNames `json:"server_names"`
|
ServerNames types.ServerNames `json:"server_names"`
|
||||||
Content map[string]interface{} `json:"content"`
|
Content map[string]interface{} `json:"content"`
|
||||||
|
Unsigned map[string]interface{} `json:"unsigned"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PerformJoinResponse struct {
|
type PerformJoinResponse struct {
|
||||||
|
|
|
@ -7,14 +7,15 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/federationapi/api"
|
|
||||||
"github.com/matrix-org/dendrite/federationapi/consumers"
|
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/version"
|
|
||||||
"github.com/matrix-org/gomatrix"
|
"github.com/matrix-org/gomatrix"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/federationapi/api"
|
||||||
|
"github.com/matrix-org/dendrite/federationapi/consumers"
|
||||||
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PerformLeaveRequest implements api.FederationInternalAPI
|
// PerformLeaveRequest implements api.FederationInternalAPI
|
||||||
|
@ -95,6 +96,7 @@ func (r *FederationInternalAPI) PerformJoin(
|
||||||
request.Content,
|
request.Content,
|
||||||
serverName,
|
serverName,
|
||||||
supportedVersions,
|
supportedVersions,
|
||||||
|
request.Unsigned,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
logrus.WithError(err).WithFields(logrus.Fields{
|
logrus.WithError(err).WithFields(logrus.Fields{
|
||||||
"server_name": serverName,
|
"server_name": serverName,
|
||||||
|
@ -139,6 +141,7 @@ func (r *FederationInternalAPI) performJoinUsingServer(
|
||||||
content map[string]interface{},
|
content map[string]interface{},
|
||||||
serverName gomatrixserverlib.ServerName,
|
serverName gomatrixserverlib.ServerName,
|
||||||
supportedVersions []gomatrixserverlib.RoomVersion,
|
supportedVersions []gomatrixserverlib.RoomVersion,
|
||||||
|
unsigned map[string]interface{},
|
||||||
) error {
|
) error {
|
||||||
// Try to perform a make_join using the information supplied in the
|
// Try to perform a make_join using the information supplied in the
|
||||||
// request.
|
// request.
|
||||||
|
@ -259,7 +262,7 @@ func (r *FederationInternalAPI) performJoinUsingServer(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("JoinedHostsFromEvents: failed to get joined hosts: %s", err)
|
return fmt.Errorf("JoinedHostsFromEvents: failed to get joined hosts: %s", err)
|
||||||
}
|
}
|
||||||
logrus.WithField("hosts", joinedHosts).WithField("room", roomID).Info("Joined federated room with hosts")
|
logrus.WithField("room", roomID).Infof("Joined federated room with %d hosts", len(joinedHosts))
|
||||||
if _, err = r.db.UpdateRoom(context.Background(), roomID, joinedHosts, nil, true); err != nil {
|
if _, err = r.db.UpdateRoom(context.Background(), roomID, joinedHosts, nil, true); err != nil {
|
||||||
return fmt.Errorf("UpdatedRoom: failed to update room with joined hosts: %s", err)
|
return fmt.Errorf("UpdatedRoom: failed to update room with joined hosts: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -267,6 +270,14 @@ func (r *FederationInternalAPI) performJoinUsingServer(
|
||||||
// If we successfully performed a send_join above then the other
|
// If we successfully performed a send_join above then the other
|
||||||
// server now thinks we're a part of the room. Send the newly
|
// server now thinks we're a part of the room. Send the newly
|
||||||
// returned state to the roomserver to update our local view.
|
// returned state to the roomserver to update our local view.
|
||||||
|
if unsigned != nil {
|
||||||
|
event, err = event.SetUnsigned(unsigned)
|
||||||
|
if err != nil {
|
||||||
|
// non-fatal, log and continue
|
||||||
|
logrus.WithError(err).Errorf("Failed to set unsigned content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err = roomserverAPI.SendEventWithState(
|
if err = roomserverAPI.SendEventWithState(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
r.rsAPI,
|
r.rsAPI,
|
||||||
|
|
|
@ -160,7 +160,7 @@ func localKeys(cfg *config.FederationAPI, validUntil time.Time) (*gomatrixserver
|
||||||
for _, oldVerifyKey := range cfg.Matrix.OldVerifyKeys {
|
for _, oldVerifyKey := range cfg.Matrix.OldVerifyKeys {
|
||||||
keys.OldVerifyKeys[oldVerifyKey.KeyID] = gomatrixserverlib.OldVerifyKey{
|
keys.OldVerifyKeys[oldVerifyKey.KeyID] = gomatrixserverlib.OldVerifyKey{
|
||||||
VerifyKey: gomatrixserverlib.VerifyKey{
|
VerifyKey: gomatrixserverlib.VerifyKey{
|
||||||
Key: gomatrixserverlib.Base64Bytes(oldVerifyKey.PrivateKey.Public().(ed25519.PublicKey)),
|
Key: oldVerifyKey.PublicKey,
|
||||||
},
|
},
|
||||||
ExpiredTS: oldVerifyKey.ExpiredAt,
|
ExpiredTS: oldVerifyKey.ExpiredAt,
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ var build string
|
||||||
const (
|
const (
|
||||||
VersionMajor = 0
|
VersionMajor = 0
|
||||||
VersionMinor = 10
|
VersionMinor = 10
|
||||||
VersionPatch = 1
|
VersionPatch = 2
|
||||||
VersionTag = "" // example: "rc1"
|
VersionTag = "" // example: "rc1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,11 @@ func (a *KeyInternalAPI) PerformUploadKeys(ctx context.Context, req *api.Perform
|
||||||
if len(req.OneTimeKeys) > 0 {
|
if len(req.OneTimeKeys) > 0 {
|
||||||
a.uploadOneTimeKeys(ctx, req, res)
|
a.uploadOneTimeKeys(ctx, req, res)
|
||||||
}
|
}
|
||||||
|
otks, err := a.DB.OneTimeKeysCount(ctx, req.UserID, req.DeviceID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res.OneTimeKeyCounts = []api.OneTimeKeysCount{*otks}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,15 +212,13 @@ func (a *KeyInternalAPI) QueryDeviceMessages(ctx context.Context, req *api.Query
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
maxStreamID := int64(0)
|
maxStreamID := int64(0)
|
||||||
|
// remove deleted devices
|
||||||
|
var result []api.DeviceMessage
|
||||||
for _, m := range msgs {
|
for _, m := range msgs {
|
||||||
if m.StreamID > maxStreamID {
|
if m.StreamID > maxStreamID {
|
||||||
maxStreamID = m.StreamID
|
maxStreamID = m.StreamID
|
||||||
}
|
}
|
||||||
}
|
if m.KeyJSON == nil || len(m.KeyJSON) == 0 {
|
||||||
// remove deleted devices
|
|
||||||
var result []api.DeviceMessage
|
|
||||||
for _, m := range msgs {
|
|
||||||
if m.KeyJSON == nil {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result = append(result, m)
|
result = append(result, m)
|
||||||
|
|
156
keyserver/internal/internal_test.go
Normal file
156
keyserver/internal/internal_test.go
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
package internal_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/keyserver/api"
|
||||||
|
"github.com/matrix-org/dendrite/keyserver/internal"
|
||||||
|
"github.com/matrix-org/dendrite/keyserver/storage"
|
||||||
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
|
"github.com/matrix-org/dendrite/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mustCreateDatabase(t *testing.T, dbType test.DBType) (storage.Database, func()) {
|
||||||
|
t.Helper()
|
||||||
|
connStr, close := test.PrepareDBConnectionString(t, dbType)
|
||||||
|
db, err := storage.NewDatabase(nil, &config.DatabaseOptions{
|
||||||
|
ConnectionString: config.DataSource(connStr),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create new user db: %v", err)
|
||||||
|
}
|
||||||
|
return db, close
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_QueryDeviceMessages(t *testing.T) {
|
||||||
|
alice := test.NewUser(t)
|
||||||
|
type args struct {
|
||||||
|
req *api.QueryDeviceMessagesRequest
|
||||||
|
res *api.QueryDeviceMessagesResponse
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
want *api.QueryDeviceMessagesResponse
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no existing keys",
|
||||||
|
args: args{
|
||||||
|
req: &api.QueryDeviceMessagesRequest{
|
||||||
|
UserID: "@doesNotExist:localhost",
|
||||||
|
},
|
||||||
|
res: &api.QueryDeviceMessagesResponse{},
|
||||||
|
},
|
||||||
|
want: &api.QueryDeviceMessagesResponse{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "existing user returns devices",
|
||||||
|
args: args{
|
||||||
|
req: &api.QueryDeviceMessagesRequest{
|
||||||
|
UserID: alice.ID,
|
||||||
|
},
|
||||||
|
res: &api.QueryDeviceMessagesResponse{},
|
||||||
|
},
|
||||||
|
want: &api.QueryDeviceMessagesResponse{
|
||||||
|
StreamID: 6,
|
||||||
|
Devices: []api.DeviceMessage{
|
||||||
|
{
|
||||||
|
Type: api.TypeDeviceKeyUpdate, StreamID: 5, DeviceKeys: &api.DeviceKeys{
|
||||||
|
DeviceID: "myDevice",
|
||||||
|
DisplayName: "first device",
|
||||||
|
UserID: alice.ID,
|
||||||
|
KeyJSON: []byte("ghi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: api.TypeDeviceKeyUpdate, StreamID: 6, DeviceKeys: &api.DeviceKeys{
|
||||||
|
DeviceID: "mySecondDevice",
|
||||||
|
DisplayName: "second device",
|
||||||
|
UserID: alice.ID,
|
||||||
|
KeyJSON: []byte("jkl"),
|
||||||
|
}, // streamID 6
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceMessages := []api.DeviceMessage{
|
||||||
|
{ // not the user we're looking for
|
||||||
|
Type: api.TypeDeviceKeyUpdate, DeviceKeys: &api.DeviceKeys{
|
||||||
|
UserID: "@doesNotExist:localhost",
|
||||||
|
},
|
||||||
|
// streamID 1 for this user
|
||||||
|
},
|
||||||
|
{ // empty keyJSON will be ignored
|
||||||
|
Type: api.TypeDeviceKeyUpdate, DeviceKeys: &api.DeviceKeys{
|
||||||
|
DeviceID: "myDevice",
|
||||||
|
UserID: alice.ID,
|
||||||
|
}, // streamID 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: api.TypeDeviceKeyUpdate, DeviceKeys: &api.DeviceKeys{
|
||||||
|
DeviceID: "myDevice",
|
||||||
|
UserID: alice.ID,
|
||||||
|
KeyJSON: []byte("abc"),
|
||||||
|
}, // streamID 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: api.TypeDeviceKeyUpdate, DeviceKeys: &api.DeviceKeys{
|
||||||
|
DeviceID: "myDevice",
|
||||||
|
UserID: alice.ID,
|
||||||
|
KeyJSON: []byte("def"),
|
||||||
|
}, // streamID 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: api.TypeDeviceKeyUpdate, DeviceKeys: &api.DeviceKeys{
|
||||||
|
DeviceID: "myDevice",
|
||||||
|
UserID: alice.ID,
|
||||||
|
KeyJSON: []byte(""),
|
||||||
|
}, // streamID 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: api.TypeDeviceKeyUpdate, DeviceKeys: &api.DeviceKeys{
|
||||||
|
DeviceID: "myDevice",
|
||||||
|
DisplayName: "first device",
|
||||||
|
UserID: alice.ID,
|
||||||
|
KeyJSON: []byte("ghi"),
|
||||||
|
}, // streamID 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: api.TypeDeviceKeyUpdate, DeviceKeys: &api.DeviceKeys{
|
||||||
|
DeviceID: "mySecondDevice",
|
||||||
|
UserID: alice.ID,
|
||||||
|
KeyJSON: []byte("jkl"),
|
||||||
|
DisplayName: "second device",
|
||||||
|
}, // streamID 6
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
||||||
|
db, closeDB := mustCreateDatabase(t, dbType)
|
||||||
|
defer closeDB()
|
||||||
|
if err := db.StoreLocalDeviceKeys(ctx, deviceMessages); err != nil {
|
||||||
|
t.Fatalf("failed to store local devicesKeys")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
a := &internal.KeyInternalAPI{
|
||||||
|
DB: db,
|
||||||
|
}
|
||||||
|
if err := a.QueryDeviceMessages(ctx, tt.args.req, tt.args.res); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("QueryDeviceMessages() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
got := tt.args.res
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("QueryDeviceMessages(): got:\n%+v, want:\n%+v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/internal"
|
"github.com/matrix-org/dendrite/internal"
|
||||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||||
"github.com/matrix-org/dendrite/keyserver/api"
|
"github.com/matrix-org/dendrite/keyserver/api"
|
||||||
|
@ -204,20 +205,17 @@ func (s *deviceKeysStatements) SelectBatchDeviceKeys(ctx context.Context, userID
|
||||||
deviceIDMap[d] = true
|
deviceIDMap[d] = true
|
||||||
}
|
}
|
||||||
var result []api.DeviceMessage
|
var result []api.DeviceMessage
|
||||||
|
var displayName sql.NullString
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
dk := api.DeviceMessage{
|
dk := api.DeviceMessage{
|
||||||
Type: api.TypeDeviceKeyUpdate,
|
Type: api.TypeDeviceKeyUpdate,
|
||||||
DeviceKeys: &api.DeviceKeys{},
|
DeviceKeys: &api.DeviceKeys{
|
||||||
|
UserID: userID,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
dk.UserID = userID
|
if err := rows.Scan(&dk.DeviceID, &dk.KeyJSON, &dk.StreamID, &displayName); err != nil {
|
||||||
var keyJSON string
|
|
||||||
var streamID int64
|
|
||||||
var displayName sql.NullString
|
|
||||||
if err := rows.Scan(&dk.DeviceID, &keyJSON, &streamID, &displayName); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
dk.KeyJSON = []byte(keyJSON)
|
|
||||||
dk.StreamID = streamID
|
|
||||||
if displayName.Valid {
|
if displayName.Valid {
|
||||||
dk.DisplayName = displayName.String
|
dk.DisplayName = displayName.String
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,21 +137,17 @@ func (s *deviceKeysStatements) SelectBatchDeviceKeys(ctx context.Context, userID
|
||||||
}
|
}
|
||||||
defer internal.CloseAndLogIfError(ctx, rows, "selectBatchDeviceKeysStmt: rows.close() failed")
|
defer internal.CloseAndLogIfError(ctx, rows, "selectBatchDeviceKeysStmt: rows.close() failed")
|
||||||
var result []api.DeviceMessage
|
var result []api.DeviceMessage
|
||||||
|
var displayName sql.NullString
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
dk := api.DeviceMessage{
|
dk := api.DeviceMessage{
|
||||||
Type: api.TypeDeviceKeyUpdate,
|
Type: api.TypeDeviceKeyUpdate,
|
||||||
DeviceKeys: &api.DeviceKeys{},
|
DeviceKeys: &api.DeviceKeys{
|
||||||
|
UserID: userID,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
dk.Type = api.TypeDeviceKeyUpdate
|
if err := rows.Scan(&dk.DeviceID, &dk.KeyJSON, &dk.StreamID, &displayName); err != nil {
|
||||||
dk.UserID = userID
|
|
||||||
var keyJSON string
|
|
||||||
var streamID int64
|
|
||||||
var displayName sql.NullString
|
|
||||||
if err := rows.Scan(&dk.DeviceID, &keyJSON, &streamID, &displayName); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
dk.KeyJSON = []byte(keyJSON)
|
|
||||||
dk.StreamID = streamID
|
|
||||||
if displayName.Valid {
|
if displayName.Valid {
|
||||||
dk.DisplayName = displayName.String
|
dk.DisplayName = displayName.String
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,7 @@ type PerformJoinRequest struct {
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"user_id"`
|
||||||
Content map[string]interface{} `json:"content"`
|
Content map[string]interface{} `json:"content"`
|
||||||
ServerNames []gomatrixserverlib.ServerName `json:"server_names"`
|
ServerNames []gomatrixserverlib.ServerName `json:"server_names"`
|
||||||
|
Unsigned map[string]interface{} `json:"unsigned"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PerformJoinResponse struct {
|
type PerformJoinResponse struct {
|
||||||
|
|
|
@ -7,6 +7,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/roomserver/auth"
|
"github.com/matrix-org/dendrite/roomserver/auth"
|
||||||
"github.com/matrix-org/dendrite/roomserver/state"
|
"github.com/matrix-org/dendrite/roomserver/state"
|
||||||
|
@ -14,8 +17,6 @@ import (
|
||||||
"github.com/matrix-org/dendrite/roomserver/storage/shared"
|
"github.com/matrix-org/dendrite/roomserver/storage/shared"
|
||||||
"github.com/matrix-org/dendrite/roomserver/storage/tables"
|
"github.com/matrix-org/dendrite/roomserver/storage/tables"
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
"github.com/matrix-org/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: temporary package which has helper functions used by both internal/perform packages.
|
// TODO: temporary package which has helper functions used by both internal/perform packages.
|
||||||
|
@ -97,35 +98,35 @@ func IsServerCurrentlyInRoom(ctx context.Context, db storage.Database, serverNam
|
||||||
func IsInvitePending(
|
func IsInvitePending(
|
||||||
ctx context.Context, db storage.Database,
|
ctx context.Context, db storage.Database,
|
||||||
roomID, userID string,
|
roomID, userID string,
|
||||||
) (bool, string, string, error) {
|
) (bool, string, string, *gomatrixserverlib.Event, error) {
|
||||||
// Look up the room NID for the supplied room ID.
|
// Look up the room NID for the supplied room ID.
|
||||||
info, err := db.RoomInfo(ctx, roomID)
|
info, err := db.RoomInfo(ctx, roomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", "", fmt.Errorf("r.DB.RoomInfo: %w", err)
|
return false, "", "", nil, fmt.Errorf("r.DB.RoomInfo: %w", err)
|
||||||
}
|
}
|
||||||
if info == nil {
|
if info == nil {
|
||||||
return false, "", "", fmt.Errorf("cannot get RoomInfo: unknown room ID %s", roomID)
|
return false, "", "", nil, fmt.Errorf("cannot get RoomInfo: unknown room ID %s", roomID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up the state key NID for the supplied user ID.
|
// Look up the state key NID for the supplied user ID.
|
||||||
targetUserNIDs, err := db.EventStateKeyNIDs(ctx, []string{userID})
|
targetUserNIDs, err := db.EventStateKeyNIDs(ctx, []string{userID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", "", fmt.Errorf("r.DB.EventStateKeyNIDs: %w", err)
|
return false, "", "", nil, fmt.Errorf("r.DB.EventStateKeyNIDs: %w", err)
|
||||||
}
|
}
|
||||||
targetUserNID, targetUserFound := targetUserNIDs[userID]
|
targetUserNID, targetUserFound := targetUserNIDs[userID]
|
||||||
if !targetUserFound {
|
if !targetUserFound {
|
||||||
return false, "", "", fmt.Errorf("missing NID for user %q (%+v)", userID, targetUserNIDs)
|
return false, "", "", nil, fmt.Errorf("missing NID for user %q (%+v)", userID, targetUserNIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's see if we have an event active for the user in the room. If
|
// Let's see if we have an event active for the user in the room. If
|
||||||
// we do then it will contain a server name that we can direct the
|
// we do then it will contain a server name that we can direct the
|
||||||
// send_leave to.
|
// send_leave to.
|
||||||
senderUserNIDs, eventIDs, err := db.GetInvitesForUser(ctx, info.RoomNID, targetUserNID)
|
senderUserNIDs, eventIDs, eventJSON, err := db.GetInvitesForUser(ctx, info.RoomNID, targetUserNID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", "", fmt.Errorf("r.DB.GetInvitesForUser: %w", err)
|
return false, "", "", nil, fmt.Errorf("r.DB.GetInvitesForUser: %w", err)
|
||||||
}
|
}
|
||||||
if len(senderUserNIDs) == 0 {
|
if len(senderUserNIDs) == 0 {
|
||||||
return false, "", "", nil
|
return false, "", "", nil, nil
|
||||||
}
|
}
|
||||||
userNIDToEventID := make(map[types.EventStateKeyNID]string)
|
userNIDToEventID := make(map[types.EventStateKeyNID]string)
|
||||||
for i, nid := range senderUserNIDs {
|
for i, nid := range senderUserNIDs {
|
||||||
|
@ -135,18 +136,20 @@ func IsInvitePending(
|
||||||
// Look up the user ID from the NID.
|
// Look up the user ID from the NID.
|
||||||
senderUsers, err := db.EventStateKeys(ctx, senderUserNIDs)
|
senderUsers, err := db.EventStateKeys(ctx, senderUserNIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", "", fmt.Errorf("r.DB.EventStateKeys: %w", err)
|
return false, "", "", nil, fmt.Errorf("r.DB.EventStateKeys: %w", err)
|
||||||
}
|
}
|
||||||
if len(senderUsers) == 0 {
|
if len(senderUsers) == 0 {
|
||||||
return false, "", "", fmt.Errorf("no senderUsers")
|
return false, "", "", nil, fmt.Errorf("no senderUsers")
|
||||||
}
|
}
|
||||||
|
|
||||||
senderUser, senderUserFound := senderUsers[senderUserNIDs[0]]
|
senderUser, senderUserFound := senderUsers[senderUserNIDs[0]]
|
||||||
if !senderUserFound {
|
if !senderUserFound {
|
||||||
return false, "", "", fmt.Errorf("missing user for NID %d (%+v)", senderUserNIDs[0], senderUsers)
|
return false, "", "", nil, fmt.Errorf("missing user for NID %d (%+v)", senderUserNIDs[0], senderUsers)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, senderUser, userNIDToEventID[senderUserNIDs[0]], nil
|
event, err := gomatrixserverlib.NewEventFromTrustedJSON(eventJSON, false, info.RoomVersion)
|
||||||
|
|
||||||
|
return true, senderUser, userNIDToEventID[senderUserNIDs[0]], event, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMembershipsAtState filters the state events to
|
// GetMembershipsAtState filters the state events to
|
||||||
|
|
|
@ -173,12 +173,15 @@ func (r *Inputer) processRoomEvent(
|
||||||
for _, server := range serverRes.ServerNames {
|
for _, server := range serverRes.ServerNames {
|
||||||
servers[server] = struct{}{}
|
servers[server] = struct{}{}
|
||||||
}
|
}
|
||||||
|
// Don't try to talk to ourselves.
|
||||||
|
delete(servers, r.Cfg.Matrix.ServerName)
|
||||||
|
// Now build up the list of servers.
|
||||||
serverRes.ServerNames = serverRes.ServerNames[:0]
|
serverRes.ServerNames = serverRes.ServerNames[:0]
|
||||||
if input.Origin != "" {
|
if input.Origin != "" && input.Origin != r.Cfg.Matrix.ServerName {
|
||||||
serverRes.ServerNames = append(serverRes.ServerNames, input.Origin)
|
serverRes.ServerNames = append(serverRes.ServerNames, input.Origin)
|
||||||
delete(servers, input.Origin)
|
delete(servers, input.Origin)
|
||||||
}
|
}
|
||||||
if senderDomain != input.Origin {
|
if senderDomain != input.Origin && senderDomain != r.Cfg.Matrix.ServerName {
|
||||||
serverRes.ServerNames = append(serverRes.ServerNames, senderDomain)
|
serverRes.ServerNames = append(serverRes.ServerNames, senderDomain)
|
||||||
delete(servers, senderDomain)
|
delete(servers, senderDomain)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/getsentry/sentry-go"
|
"github.com/getsentry/sentry-go"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
|
||||||
fsAPI "github.com/matrix-org/dendrite/federationapi/api"
|
fsAPI "github.com/matrix-org/dendrite/federationapi/api"
|
||||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
@ -32,8 +36,6 @@ import (
|
||||||
"github.com/matrix-org/dendrite/roomserver/storage"
|
"github.com/matrix-org/dendrite/roomserver/storage"
|
||||||
"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/gomatrixserverlib"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Joiner struct {
|
type Joiner struct {
|
||||||
|
@ -236,7 +238,7 @@ func (r *Joiner) performJoinRoomByID(
|
||||||
|
|
||||||
// Force a federated join if we're dealing with a pending invite
|
// Force a federated join if we're dealing with a pending invite
|
||||||
// and we aren't in the room.
|
// and we aren't in the room.
|
||||||
isInvitePending, inviteSender, _, err := helpers.IsInvitePending(ctx, r.DB, req.RoomIDOrAlias, req.UserID)
|
isInvitePending, inviteSender, _, inviteEvent, err := helpers.IsInvitePending(ctx, r.DB, req.RoomIDOrAlias, req.UserID)
|
||||||
if err == nil && !serverInRoom && isInvitePending {
|
if err == nil && !serverInRoom && isInvitePending {
|
||||||
_, inviterDomain, ierr := gomatrixserverlib.SplitID('@', inviteSender)
|
_, inviterDomain, ierr := gomatrixserverlib.SplitID('@', inviteSender)
|
||||||
if ierr != nil {
|
if ierr != nil {
|
||||||
|
@ -248,6 +250,17 @@ func (r *Joiner) performJoinRoomByID(
|
||||||
if inviterDomain != r.Cfg.Matrix.ServerName {
|
if inviterDomain != r.Cfg.Matrix.ServerName {
|
||||||
req.ServerNames = append(req.ServerNames, inviterDomain)
|
req.ServerNames = append(req.ServerNames, inviterDomain)
|
||||||
forceFederatedJoin = true
|
forceFederatedJoin = true
|
||||||
|
memberEvent := gjson.Parse(string(inviteEvent.JSON()))
|
||||||
|
// only set unsigned if we've got a content.membership, which we _should_
|
||||||
|
if memberEvent.Get("content.membership").Exists() {
|
||||||
|
req.Unsigned = map[string]interface{}{
|
||||||
|
"prev_sender": memberEvent.Get("sender").Str,
|
||||||
|
"prev_content": map[string]interface{}{
|
||||||
|
"is_direct": memberEvent.Get("content.is_direct").Bool(),
|
||||||
|
"membership": memberEvent.Get("content.membership").Str,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,6 +361,7 @@ func (r *Joiner) performFederatedJoinRoomByID(
|
||||||
UserID: req.UserID, // the user ID joining the room
|
UserID: req.UserID, // the user ID joining the room
|
||||||
ServerNames: req.ServerNames, // the server to try joining with
|
ServerNames: req.ServerNames, // the server to try joining with
|
||||||
Content: req.Content, // the membership event content
|
Content: req.Content, // the membership event content
|
||||||
|
Unsigned: req.Unsigned, // the unsigned event content, if any
|
||||||
}
|
}
|
||||||
fedRes := fsAPI.PerformJoinResponse{}
|
fedRes := fsAPI.PerformJoinResponse{}
|
||||||
r.FSAPI.PerformJoin(ctx, &fedReq, &fedRes)
|
r.FSAPI.PerformJoin(ctx, &fedReq, &fedRes)
|
||||||
|
|
|
@ -79,7 +79,7 @@ func (r *Leaver) performLeaveRoomByID(
|
||||||
) ([]api.OutputEvent, error) {
|
) ([]api.OutputEvent, error) {
|
||||||
// If there's an invite outstanding for the room then respond to
|
// If there's an invite outstanding for the room then respond to
|
||||||
// that.
|
// that.
|
||||||
isInvitePending, senderUser, eventID, err := helpers.IsInvitePending(ctx, r.DB, req.RoomID, req.UserID)
|
isInvitePending, senderUser, eventID, _, err := helpers.IsInvitePending(ctx, r.DB, req.RoomID, req.UserID)
|
||||||
if err == nil && isInvitePending {
|
if err == nil && isInvitePending {
|
||||||
_, senderDomain, serr := gomatrixserverlib.SplitID('@', senderUser)
|
_, senderDomain, serr := gomatrixserverlib.SplitID('@', senderUser)
|
||||||
if serr != nil {
|
if serr != nil {
|
||||||
|
|
|
@ -872,7 +872,7 @@ func (r *Queryer) QueryRestrictedJoinAllowed(ctx context.Context, req *api.Query
|
||||||
// but we don't specify an authorised via user, since the event auth
|
// but we don't specify an authorised via user, since the event auth
|
||||||
// will allow the join anyway.
|
// will allow the join anyway.
|
||||||
var pending bool
|
var pending bool
|
||||||
if pending, _, _, err = helpers.IsInvitePending(ctx, r.DB, req.RoomID, req.UserID); err != nil {
|
if pending, _, _, _, err = helpers.IsInvitePending(ctx, r.DB, req.RoomID, req.UserID); err != nil {
|
||||||
return fmt.Errorf("helpers.IsInvitePending: %w", err)
|
return fmt.Errorf("helpers.IsInvitePending: %w", err)
|
||||||
} else if pending {
|
} else if pending {
|
||||||
res.Allowed = true
|
res.Allowed = true
|
||||||
|
|
|
@ -17,10 +17,11 @@ package storage
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/storage/shared"
|
"github.com/matrix-org/dendrite/roomserver/storage/shared"
|
||||||
"github.com/matrix-org/dendrite/roomserver/storage/tables"
|
"github.com/matrix-org/dendrite/roomserver/storage/tables"
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Database interface {
|
type Database interface {
|
||||||
|
@ -104,7 +105,7 @@ type Database interface {
|
||||||
// Look up the active invites targeting a user in a room and return the
|
// Look up the active invites targeting a user in a room and return the
|
||||||
// numeric state key IDs for the user IDs who sent them along with the event IDs for the invites.
|
// numeric state key IDs for the user IDs who sent them along with the event IDs for the invites.
|
||||||
// Returns an error if there was a problem talking to the database.
|
// Returns an error if there was a problem talking to the database.
|
||||||
GetInvitesForUser(ctx context.Context, roomNID types.RoomNID, targetUserNID types.EventStateKeyNID) (senderUserIDs []types.EventStateKeyNID, eventIDs []string, err error)
|
GetInvitesForUser(ctx context.Context, roomNID types.RoomNID, targetUserNID types.EventStateKeyNID) (senderUserIDs []types.EventStateKeyNID, eventIDs []string, inviteEventJSON []byte, err error)
|
||||||
// Save a given room alias with the room ID it refers to.
|
// Save a given room alias with the room ID it refers to.
|
||||||
// Returns an error if there was a problem talking to the database.
|
// Returns an error if there was a problem talking to the database.
|
||||||
SetRoomAlias(ctx context.Context, alias string, roomID string, creatorUserID string) error
|
SetRoomAlias(ctx context.Context, alias string, roomID string, creatorUserID string) error
|
||||||
|
|
|
@ -61,7 +61,7 @@ const insertInviteEventSQL = "" +
|
||||||
" ON CONFLICT DO NOTHING"
|
" ON CONFLICT DO NOTHING"
|
||||||
|
|
||||||
const selectInviteActiveForUserInRoomSQL = "" +
|
const selectInviteActiveForUserInRoomSQL = "" +
|
||||||
"SELECT invite_event_id, sender_nid FROM roomserver_invites" +
|
"SELECT invite_event_id, sender_nid, invite_event_json FROM roomserver_invites" +
|
||||||
" WHERE target_nid = $1 AND room_nid = $2" +
|
" WHERE target_nid = $1 AND room_nid = $2" +
|
||||||
" AND NOT retired"
|
" AND NOT retired"
|
||||||
|
|
||||||
|
@ -141,25 +141,26 @@ func (s *inviteStatements) UpdateInviteRetired(
|
||||||
func (s *inviteStatements) SelectInviteActiveForUserInRoom(
|
func (s *inviteStatements) SelectInviteActiveForUserInRoom(
|
||||||
ctx context.Context, txn *sql.Tx,
|
ctx context.Context, txn *sql.Tx,
|
||||||
targetUserNID types.EventStateKeyNID, roomNID types.RoomNID,
|
targetUserNID types.EventStateKeyNID, roomNID types.RoomNID,
|
||||||
) ([]types.EventStateKeyNID, []string, error) {
|
) ([]types.EventStateKeyNID, []string, []byte, error) {
|
||||||
stmt := sqlutil.TxStmt(txn, s.selectInviteActiveForUserInRoomStmt)
|
stmt := sqlutil.TxStmt(txn, s.selectInviteActiveForUserInRoomStmt)
|
||||||
rows, err := stmt.QueryContext(
|
rows, err := stmt.QueryContext(
|
||||||
ctx, targetUserNID, roomNID,
|
ctx, targetUserNID, roomNID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
defer internal.CloseAndLogIfError(ctx, rows, "selectInviteActiveForUserInRoom: rows.close() failed")
|
defer internal.CloseAndLogIfError(ctx, rows, "selectInviteActiveForUserInRoom: rows.close() failed")
|
||||||
var result []types.EventStateKeyNID
|
var result []types.EventStateKeyNID
|
||||||
var eventIDs []string
|
var eventIDs []string
|
||||||
var inviteEventID string
|
var inviteEventID string
|
||||||
var senderUserNID int64
|
var senderUserNID int64
|
||||||
|
var eventJSON []byte
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
if err := rows.Scan(&inviteEventID, &senderUserNID); err != nil {
|
if err := rows.Scan(&inviteEventID, &senderUserNID, &eventJSON); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
result = append(result, types.EventStateKeyNID(senderUserNID))
|
result = append(result, types.EventStateKeyNID(senderUserNID))
|
||||||
eventIDs = append(eventIDs, inviteEventID)
|
eventIDs = append(eventIDs, inviteEventID)
|
||||||
}
|
}
|
||||||
return result, eventIDs, rows.Err()
|
return result, eventIDs, eventJSON, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
|
||||||
"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/roomserver/storage/tables"
|
"github.com/matrix-org/dendrite/roomserver/storage/tables"
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
"github.com/matrix-org/util"
|
|
||||||
"github.com/tidwall/gjson"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ideally, when we have both events we should redact the event JSON and forget about the redaction, but we currently
|
// Ideally, when we have both events we should redact the event JSON and forget about the redaction, but we currently
|
||||||
|
@ -445,7 +446,7 @@ func (d *Database) GetInvitesForUser(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
roomNID types.RoomNID,
|
roomNID types.RoomNID,
|
||||||
targetUserNID types.EventStateKeyNID,
|
targetUserNID types.EventStateKeyNID,
|
||||||
) (senderUserIDs []types.EventStateKeyNID, eventIDs []string, err error) {
|
) (senderUserIDs []types.EventStateKeyNID, eventIDs []string, inviteEventJSON []byte, err error) {
|
||||||
return d.InvitesTable.SelectInviteActiveForUserInRoom(ctx, nil, targetUserNID, roomNID)
|
return d.InvitesTable.SelectInviteActiveForUserInRoom(ctx, nil, targetUserNID, roomNID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ const insertInviteEventSQL = "" +
|
||||||
" ON CONFLICT DO NOTHING"
|
" ON CONFLICT DO NOTHING"
|
||||||
|
|
||||||
const selectInviteActiveForUserInRoomSQL = "" +
|
const selectInviteActiveForUserInRoomSQL = "" +
|
||||||
"SELECT invite_event_id, sender_nid FROM roomserver_invites" +
|
"SELECT invite_event_id, sender_nid, invite_event_json FROM roomserver_invites" +
|
||||||
" WHERE target_nid = $1 AND room_nid = $2" +
|
" WHERE target_nid = $1 AND room_nid = $2" +
|
||||||
" AND NOT retired"
|
" AND NOT retired"
|
||||||
|
|
||||||
|
@ -136,25 +136,26 @@ func (s *inviteStatements) UpdateInviteRetired(
|
||||||
func (s *inviteStatements) SelectInviteActiveForUserInRoom(
|
func (s *inviteStatements) SelectInviteActiveForUserInRoom(
|
||||||
ctx context.Context, txn *sql.Tx,
|
ctx context.Context, txn *sql.Tx,
|
||||||
targetUserNID types.EventStateKeyNID, roomNID types.RoomNID,
|
targetUserNID types.EventStateKeyNID, roomNID types.RoomNID,
|
||||||
) ([]types.EventStateKeyNID, []string, error) {
|
) ([]types.EventStateKeyNID, []string, []byte, error) {
|
||||||
stmt := sqlutil.TxStmt(txn, s.selectInviteActiveForUserInRoomStmt)
|
stmt := sqlutil.TxStmt(txn, s.selectInviteActiveForUserInRoomStmt)
|
||||||
rows, err := stmt.QueryContext(
|
rows, err := stmt.QueryContext(
|
||||||
ctx, targetUserNID, roomNID,
|
ctx, targetUserNID, roomNID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
defer internal.CloseAndLogIfError(ctx, rows, "selectInviteActiveForUserInRoom: rows.close() failed")
|
defer internal.CloseAndLogIfError(ctx, rows, "selectInviteActiveForUserInRoom: rows.close() failed")
|
||||||
var result []types.EventStateKeyNID
|
var result []types.EventStateKeyNID
|
||||||
var eventIDs []string
|
var eventIDs []string
|
||||||
var eventID string
|
var eventID string
|
||||||
var senderUserNID int64
|
var senderUserNID int64
|
||||||
|
var eventJSON []byte
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
if err := rows.Scan(&eventID, &senderUserNID); err != nil {
|
if err := rows.Scan(&eventID, &senderUserNID, &eventJSON); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
result = append(result, types.EventStateKeyNID(senderUserNID))
|
result = append(result, types.EventStateKeyNID(senderUserNID))
|
||||||
eventIDs = append(eventIDs, eventID)
|
eventIDs = append(eventIDs, eventID)
|
||||||
}
|
}
|
||||||
return result, eventIDs, nil
|
return result, eventIDs, eventJSON, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ type Invites interface {
|
||||||
InsertInviteEvent(ctx context.Context, txn *sql.Tx, inviteEventID string, roomNID types.RoomNID, targetUserNID, senderUserNID types.EventStateKeyNID, inviteEventJSON []byte) (bool, error)
|
InsertInviteEvent(ctx context.Context, txn *sql.Tx, inviteEventID string, roomNID types.RoomNID, targetUserNID, senderUserNID types.EventStateKeyNID, inviteEventJSON []byte) (bool, error)
|
||||||
UpdateInviteRetired(ctx context.Context, txn *sql.Tx, roomNID types.RoomNID, targetUserNID types.EventStateKeyNID) ([]string, error)
|
UpdateInviteRetired(ctx context.Context, txn *sql.Tx, roomNID types.RoomNID, targetUserNID types.EventStateKeyNID) ([]string, error)
|
||||||
// SelectInviteActiveForUserInRoom returns a list of sender state key NIDs and invite event IDs matching those nids.
|
// SelectInviteActiveForUserInRoom returns a list of sender state key NIDs and invite event IDs matching those nids.
|
||||||
SelectInviteActiveForUserInRoom(ctx context.Context, txn *sql.Tx, targetUserNID types.EventStateKeyNID, roomNID types.RoomNID) ([]types.EventStateKeyNID, []string, error)
|
SelectInviteActiveForUserInRoom(ctx context.Context, txn *sql.Tx, targetUserNID types.EventStateKeyNID, roomNID types.RoomNID) ([]types.EventStateKeyNID, []string, []byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type MembershipState int64
|
type MembershipState int64
|
||||||
|
|
|
@ -4,6 +4,9 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||||
"github.com/matrix-org/dendrite/roomserver/storage/postgres"
|
"github.com/matrix-org/dendrite/roomserver/storage/postgres"
|
||||||
"github.com/matrix-org/dendrite/roomserver/storage/sqlite3"
|
"github.com/matrix-org/dendrite/roomserver/storage/sqlite3"
|
||||||
|
@ -11,8 +14,6 @@ import (
|
||||||
"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/test"
|
"github.com/matrix-org/dendrite/test"
|
||||||
"github.com/matrix-org/util"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func mustCreateInviteTable(t *testing.T, dbType test.DBType) (tables.Invites, func()) {
|
func mustCreateInviteTable(t *testing.T, dbType test.DBType) (tables.Invites, func()) {
|
||||||
|
@ -67,7 +68,7 @@ func TestInviteTable(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, newInvite)
|
assert.True(t, newInvite)
|
||||||
|
|
||||||
stateKeyNIDs, eventIDs, err := tab.SelectInviteActiveForUserInRoom(ctx, nil, targetUserNID, roomNID)
|
stateKeyNIDs, eventIDs, _, err := tab.SelectInviteActiveForUserInRoom(ctx, nil, targetUserNID, roomNID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, []string{eventID1, eventID2}, eventIDs)
|
assert.Equal(t, []string{eventID1, eventID2}, eventIDs)
|
||||||
assert.Equal(t, []types.EventStateKeyNID{2, 2}, stateKeyNIDs)
|
assert.Equal(t, []types.EventStateKeyNID{2, 2}, stateKeyNIDs)
|
||||||
|
@ -78,13 +79,13 @@ func TestInviteTable(t *testing.T) {
|
||||||
assert.Equal(t, []string{eventID1, eventID2}, retiredEventIDs)
|
assert.Equal(t, []string{eventID1, eventID2}, retiredEventIDs)
|
||||||
|
|
||||||
// This should now be empty
|
// This should now be empty
|
||||||
stateKeyNIDs, eventIDs, err = tab.SelectInviteActiveForUserInRoom(ctx, nil, targetUserNID, roomNID)
|
stateKeyNIDs, eventIDs, _, err = tab.SelectInviteActiveForUserInRoom(ctx, nil, targetUserNID, roomNID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Empty(t, eventIDs)
|
assert.Empty(t, eventIDs)
|
||||||
assert.Empty(t, stateKeyNIDs)
|
assert.Empty(t, stateKeyNIDs)
|
||||||
|
|
||||||
// Non-existent targetUserNID
|
// Non-existent targetUserNID
|
||||||
stateKeyNIDs, eventIDs, err = tab.SelectInviteActiveForUserInRoom(ctx, nil, types.EventStateKeyNID(10), roomNID)
|
stateKeyNIDs, eventIDs, _, err = tab.SelectInviteActiveForUserInRoom(ctx, nil, types.EventStateKeyNID(10), roomNID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Empty(t, stateKeyNIDs)
|
assert.Empty(t, stateKeyNIDs)
|
||||||
assert.Empty(t, eventIDs)
|
assert.Empty(t, eventIDs)
|
||||||
|
|
|
@ -231,13 +231,14 @@ func loadConfig(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, oldPrivateKey := range c.Global.OldVerifyKeys {
|
for _, key := range c.Global.OldVerifyKeys {
|
||||||
|
switch {
|
||||||
|
case key.PrivateKeyPath != "":
|
||||||
var oldPrivateKeyData []byte
|
var oldPrivateKeyData []byte
|
||||||
|
oldPrivateKeyPath := absPath(basePath, key.PrivateKeyPath)
|
||||||
oldPrivateKeyPath := absPath(basePath, oldPrivateKey.PrivateKeyPath)
|
|
||||||
oldPrivateKeyData, err = readFile(oldPrivateKeyPath)
|
oldPrivateKeyData, err = readFile(oldPrivateKeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to read %q: %w", oldPrivateKeyPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTSPEC: Ordinarily we should enforce key ID formatting, but since there are
|
// NOTSPEC: Ordinarily we should enforce key ID formatting, but since there are
|
||||||
|
@ -245,10 +246,25 @@ func loadConfig(
|
||||||
// to lack of validation in Synapse, we won't enforce that for old verify keys.
|
// to lack of validation in Synapse, we won't enforce that for old verify keys.
|
||||||
keyID, privateKey, perr := readKeyPEM(oldPrivateKeyPath, oldPrivateKeyData, false)
|
keyID, privateKey, perr := readKeyPEM(oldPrivateKeyPath, oldPrivateKeyData, false)
|
||||||
if perr != nil {
|
if perr != nil {
|
||||||
return nil, perr
|
return nil, fmt.Errorf("failed to parse %q: %w", oldPrivateKeyPath, perr)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Global.OldVerifyKeys[i].KeyID, c.Global.OldVerifyKeys[i].PrivateKey = keyID, privateKey
|
key.KeyID = keyID
|
||||||
|
key.PrivateKey = privateKey
|
||||||
|
key.PublicKey = gomatrixserverlib.Base64Bytes(privateKey.Public().(ed25519.PublicKey))
|
||||||
|
|
||||||
|
case key.KeyID == "":
|
||||||
|
return nil, fmt.Errorf("'key_id' must be specified if 'public_key' is specified")
|
||||||
|
|
||||||
|
case len(key.PublicKey) == ed25519.PublicKeySize:
|
||||||
|
continue
|
||||||
|
|
||||||
|
case len(key.PublicKey) > 0:
|
||||||
|
return nil, fmt.Errorf("the supplied 'public_key' is the wrong length")
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("either specify a 'private_key' path or supply both 'public_key' and 'key_id'")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.MediaAPI.AbsBasePath = Path(absPath(basePath, c.MediaAPI.BasePath))
|
c.MediaAPI.AbsBasePath = Path(absPath(basePath, c.MediaAPI.BasePath))
|
||||||
|
|
|
@ -27,7 +27,7 @@ type Global struct {
|
||||||
// Information about old private keys that used to be used to sign requests and
|
// Information about old private keys that used to be used to sign requests and
|
||||||
// events on this domain. They will not be used but will be advertised to other
|
// events on this domain. They will not be used but will be advertised to other
|
||||||
// servers that ask for them to help verify old events.
|
// servers that ask for them to help verify old events.
|
||||||
OldVerifyKeys []OldVerifyKeys `yaml:"old_private_keys"`
|
OldVerifyKeys []*OldVerifyKeys `yaml:"old_private_keys"`
|
||||||
|
|
||||||
// How long a remote server can cache our server key for before requesting it again.
|
// How long a remote server can cache our server key for before requesting it again.
|
||||||
// Increasing this number will reduce the number of requests made by remote servers
|
// Increasing this number will reduce the number of requests made by remote servers
|
||||||
|
@ -127,8 +127,11 @@ type OldVerifyKeys struct {
|
||||||
// The private key itself.
|
// The private key itself.
|
||||||
PrivateKey ed25519.PrivateKey `yaml:"-"`
|
PrivateKey ed25519.PrivateKey `yaml:"-"`
|
||||||
|
|
||||||
|
// The public key, in case only that part is known.
|
||||||
|
PublicKey gomatrixserverlib.Base64Bytes `yaml:"public_key"`
|
||||||
|
|
||||||
// The key ID of the private key.
|
// The key ID of the private key.
|
||||||
KeyID gomatrixserverlib.KeyID `yaml:"-"`
|
KeyID gomatrixserverlib.KeyID `yaml:"key_id"`
|
||||||
|
|
||||||
// When the private key was designed as "expired", as a UNIX timestamp
|
// When the private key was designed as "expired", as a UNIX timestamp
|
||||||
// in millisecond precision.
|
// in millisecond precision.
|
||||||
|
|
|
@ -19,11 +19,13 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
|
||||||
"github.com/matrix-org/dendrite/syncapi/storage"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -189,7 +191,7 @@ func visibilityForEvents(
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
}, membershipResp)
|
}, membershipResp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
logrus.WithError(err).Error("visibilityForEvents: failed to fetch membership at event, defaulting to 'leave'")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a map from eventID -> eventVisibility
|
// Create a map from eventID -> eventVisibility
|
||||||
|
|
|
@ -48,6 +48,7 @@ type Notifier struct {
|
||||||
lastCleanUpTime time.Time
|
lastCleanUpTime time.Time
|
||||||
// This map is reused to prevent allocations and GC pressure in SharedUsers.
|
// This map is reused to prevent allocations and GC pressure in SharedUsers.
|
||||||
_sharedUserMap map[string]struct{}
|
_sharedUserMap map[string]struct{}
|
||||||
|
_wakeupUserMap map[string]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNotifier creates a new notifier set to the given sync position.
|
// NewNotifier creates a new notifier set to the given sync position.
|
||||||
|
@ -61,6 +62,7 @@ func NewNotifier() *Notifier {
|
||||||
lock: &sync.RWMutex{},
|
lock: &sync.RWMutex{},
|
||||||
lastCleanUpTime: time.Now(),
|
lastCleanUpTime: time.Now(),
|
||||||
_sharedUserMap: map[string]struct{}{},
|
_sharedUserMap: map[string]struct{}{},
|
||||||
|
_wakeupUserMap: map[string]struct{}{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -408,12 +410,16 @@ func (n *Notifier) setPeekingDevices(roomIDToPeekingDevices map[string][]types.P
|
||||||
// specified user IDs, and also the specified peekingDevices
|
// specified user IDs, and also the specified peekingDevices
|
||||||
func (n *Notifier) _wakeupUsers(userIDs []string, peekingDevices []types.PeekingDevice, newPos types.StreamingToken) {
|
func (n *Notifier) _wakeupUsers(userIDs []string, peekingDevices []types.PeekingDevice, newPos types.StreamingToken) {
|
||||||
for _, userID := range userIDs {
|
for _, userID := range userIDs {
|
||||||
|
n._wakeupUserMap[userID] = struct{}{}
|
||||||
|
}
|
||||||
|
for userID := range n._wakeupUserMap {
|
||||||
for _, stream := range n._fetchUserStreams(userID) {
|
for _, stream := range n._fetchUserStreams(userID) {
|
||||||
if stream == nil {
|
if stream == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
stream.Broadcast(newPos) // wake up all goroutines Wait()ing on this stream
|
stream.Broadcast(newPos) // wake up all goroutines Wait()ing on this stream
|
||||||
}
|
}
|
||||||
|
delete(n._wakeupUserMap, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, peekingDevice := range peekingDevices {
|
for _, peekingDevice := range peekingDevices {
|
||||||
|
|
102
syncapi/routing/getevent.go
Normal file
102
syncapi/routing/getevent.go
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package routing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
|
"github.com/matrix-org/dendrite/syncapi/internal"
|
||||||
|
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||||
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetEvent implements
|
||||||
|
//
|
||||||
|
// GET /_matrix/client/r0/rooms/{roomId}/event/{eventId}
|
||||||
|
//
|
||||||
|
// https://spec.matrix.org/v1.4/client-server-api/#get_matrixclientv3roomsroomideventeventid
|
||||||
|
func GetEvent(
|
||||||
|
req *http.Request,
|
||||||
|
device *userapi.Device,
|
||||||
|
roomID string,
|
||||||
|
eventID string,
|
||||||
|
cfg *config.SyncAPI,
|
||||||
|
syncDB storage.Database,
|
||||||
|
rsAPI api.SyncRoomserverAPI,
|
||||||
|
) util.JSONResponse {
|
||||||
|
ctx := req.Context()
|
||||||
|
db, err := syncDB.NewDatabaseTransaction(ctx)
|
||||||
|
logger := util.GetLogger(ctx).WithFields(logrus.Fields{
|
||||||
|
"event_id": eventID,
|
||||||
|
"room_id": roomID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.WithError(err).Error("GetEvent: syncDB.NewDatabaseTransaction failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
|
}
|
||||||
|
|
||||||
|
events, err := db.Events(ctx, []string{eventID})
|
||||||
|
if err != nil {
|
||||||
|
logger.WithError(err).Error("GetEvent: syncDB.Events failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
|
}
|
||||||
|
|
||||||
|
// The requested event does not exist in our database
|
||||||
|
if len(events) == 0 {
|
||||||
|
logger.Debugf("GetEvent: requested event doesn't exist locally")
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
JSON: jsonerror.NotFound("The event was not found or you do not have permission to read this event"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the request is coming from an appservice, get the user from the request
|
||||||
|
userID := device.UserID
|
||||||
|
if asUserID := req.FormValue("user_id"); device.AppserviceID != "" && asUserID != "" {
|
||||||
|
userID = asUserID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply history visibility to determine if the user is allowed to view the event
|
||||||
|
events, err = internal.ApplyHistoryVisibilityFilter(ctx, db, rsAPI, events, nil, userID, "event")
|
||||||
|
if err != nil {
|
||||||
|
logger.WithError(err).Error("GetEvent: internal.ApplyHistoryVisibilityFilter failed")
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: jsonerror.InternalServerError(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only ever expect there to be one event
|
||||||
|
if len(events) != 1 {
|
||||||
|
// 0 events -> not allowed to view event; > 1 events -> something that shouldn't happen
|
||||||
|
logger.WithField("event_count", len(events)).Debug("GetEvent: can't return the requested event")
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
JSON: jsonerror.NotFound("The event was not found or you do not have permission to read this event"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: gomatrixserverlib.HeaderedToClientEvent(events[0], gomatrixserverlib.FormatAll),
|
||||||
|
}
|
||||||
|
}
|
|
@ -60,6 +60,16 @@ func Setup(
|
||||||
return OnIncomingMessagesRequest(req, syncDB, vars["roomID"], device, rsAPI, cfg, srp, lazyLoadCache)
|
return OnIncomingMessagesRequest(req, syncDB, vars["roomID"], device, rsAPI, cfg, srp, lazyLoadCache)
|
||||||
})).Methods(http.MethodGet, http.MethodOptions)
|
})).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
|
v3mux.Handle("/rooms/{roomID}/event/{eventID}",
|
||||||
|
httputil.MakeAuthAPI("rooms_get_event", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
if err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
return GetEvent(req, device, vars["roomID"], vars["eventID"], cfg, syncDB, rsAPI)
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
v3mux.Handle("/user/{userId}/filter",
|
v3mux.Handle("/user/{userId}/filter",
|
||||||
httputil.MakeAuthAPI("put_filter", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("put_filter", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
|
@ -77,9 +77,9 @@ func (p *ReceiptStreamProvider) IncrementalSync(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
jr := types.NewJoinResponse()
|
jr, ok := req.Response.Rooms.Join[roomID]
|
||||||
if existing, ok := req.Response.Rooms.Join[roomID]; ok {
|
if !ok {
|
||||||
jr = existing
|
jr = types.NewJoinResponse()
|
||||||
}
|
}
|
||||||
|
|
||||||
ev := gomatrixserverlib.ClientEvent{
|
ev := gomatrixserverlib.ClientEvent{
|
||||||
|
|
|
@ -407,7 +407,7 @@ func (rp *RequestPool) OnIncomingSyncRequest(req *http.Request, device *userapi.
|
||||||
func(txn storage.DatabaseTransaction) types.StreamPosition {
|
func(txn storage.DatabaseTransaction) types.StreamPosition {
|
||||||
return rp.streams.PDUStreamProvider.IncrementalSync(
|
return rp.streams.PDUStreamProvider.IncrementalSync(
|
||||||
syncReq.Context, txn, syncReq,
|
syncReq.Context, txn, syncReq,
|
||||||
syncReq.Since.PDUPosition, currentPos.PDUPosition,
|
syncReq.Since.PDUPosition, rp.Notifier.CurrentPosition().PDUPosition,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -416,7 +416,7 @@ func (rp *RequestPool) OnIncomingSyncRequest(req *http.Request, device *userapi.
|
||||||
func(txn storage.DatabaseTransaction) types.StreamPosition {
|
func(txn storage.DatabaseTransaction) types.StreamPosition {
|
||||||
return rp.streams.TypingStreamProvider.IncrementalSync(
|
return rp.streams.TypingStreamProvider.IncrementalSync(
|
||||||
syncReq.Context, txn, syncReq,
|
syncReq.Context, txn, syncReq,
|
||||||
syncReq.Since.TypingPosition, currentPos.TypingPosition,
|
syncReq.Since.TypingPosition, rp.Notifier.CurrentPosition().TypingPosition,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -425,7 +425,7 @@ func (rp *RequestPool) OnIncomingSyncRequest(req *http.Request, device *userapi.
|
||||||
func(txn storage.DatabaseTransaction) types.StreamPosition {
|
func(txn storage.DatabaseTransaction) types.StreamPosition {
|
||||||
return rp.streams.ReceiptStreamProvider.IncrementalSync(
|
return rp.streams.ReceiptStreamProvider.IncrementalSync(
|
||||||
syncReq.Context, txn, syncReq,
|
syncReq.Context, txn, syncReq,
|
||||||
syncReq.Since.ReceiptPosition, currentPos.ReceiptPosition,
|
syncReq.Since.ReceiptPosition, rp.Notifier.CurrentPosition().ReceiptPosition,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -434,7 +434,7 @@ func (rp *RequestPool) OnIncomingSyncRequest(req *http.Request, device *userapi.
|
||||||
func(txn storage.DatabaseTransaction) types.StreamPosition {
|
func(txn storage.DatabaseTransaction) types.StreamPosition {
|
||||||
return rp.streams.InviteStreamProvider.IncrementalSync(
|
return rp.streams.InviteStreamProvider.IncrementalSync(
|
||||||
syncReq.Context, txn, syncReq,
|
syncReq.Context, txn, syncReq,
|
||||||
syncReq.Since.InvitePosition, currentPos.InvitePosition,
|
syncReq.Since.InvitePosition, rp.Notifier.CurrentPosition().InvitePosition,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -443,7 +443,7 @@ func (rp *RequestPool) OnIncomingSyncRequest(req *http.Request, device *userapi.
|
||||||
func(txn storage.DatabaseTransaction) types.StreamPosition {
|
func(txn storage.DatabaseTransaction) types.StreamPosition {
|
||||||
return rp.streams.SendToDeviceStreamProvider.IncrementalSync(
|
return rp.streams.SendToDeviceStreamProvider.IncrementalSync(
|
||||||
syncReq.Context, txn, syncReq,
|
syncReq.Context, txn, syncReq,
|
||||||
syncReq.Since.SendToDevicePosition, currentPos.SendToDevicePosition,
|
syncReq.Since.SendToDevicePosition, rp.Notifier.CurrentPosition().SendToDevicePosition,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -452,7 +452,7 @@ func (rp *RequestPool) OnIncomingSyncRequest(req *http.Request, device *userapi.
|
||||||
func(txn storage.DatabaseTransaction) types.StreamPosition {
|
func(txn storage.DatabaseTransaction) types.StreamPosition {
|
||||||
return rp.streams.AccountDataStreamProvider.IncrementalSync(
|
return rp.streams.AccountDataStreamProvider.IncrementalSync(
|
||||||
syncReq.Context, txn, syncReq,
|
syncReq.Context, txn, syncReq,
|
||||||
syncReq.Since.AccountDataPosition, currentPos.AccountDataPosition,
|
syncReq.Since.AccountDataPosition, rp.Notifier.CurrentPosition().AccountDataPosition,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -461,7 +461,7 @@ func (rp *RequestPool) OnIncomingSyncRequest(req *http.Request, device *userapi.
|
||||||
func(txn storage.DatabaseTransaction) types.StreamPosition {
|
func(txn storage.DatabaseTransaction) types.StreamPosition {
|
||||||
return rp.streams.NotificationDataStreamProvider.IncrementalSync(
|
return rp.streams.NotificationDataStreamProvider.IncrementalSync(
|
||||||
syncReq.Context, txn, syncReq,
|
syncReq.Context, txn, syncReq,
|
||||||
syncReq.Since.NotificationDataPosition, currentPos.NotificationDataPosition,
|
syncReq.Since.NotificationDataPosition, rp.Notifier.CurrentPosition().NotificationDataPosition,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -470,7 +470,7 @@ func (rp *RequestPool) OnIncomingSyncRequest(req *http.Request, device *userapi.
|
||||||
func(txn storage.DatabaseTransaction) types.StreamPosition {
|
func(txn storage.DatabaseTransaction) types.StreamPosition {
|
||||||
return rp.streams.DeviceListStreamProvider.IncrementalSync(
|
return rp.streams.DeviceListStreamProvider.IncrementalSync(
|
||||||
syncReq.Context, txn, syncReq,
|
syncReq.Context, txn, syncReq,
|
||||||
syncReq.Since.DeviceListPosition, currentPos.DeviceListPosition,
|
syncReq.Since.DeviceListPosition, rp.Notifier.CurrentPosition().DeviceListPosition,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -479,7 +479,7 @@ func (rp *RequestPool) OnIncomingSyncRequest(req *http.Request, device *userapi.
|
||||||
func(txn storage.DatabaseTransaction) types.StreamPosition {
|
func(txn storage.DatabaseTransaction) types.StreamPosition {
|
||||||
return rp.streams.PresenceStreamProvider.IncrementalSync(
|
return rp.streams.PresenceStreamProvider.IncrementalSync(
|
||||||
syncReq.Context, txn, syncReq,
|
syncReq.Context, txn, syncReq,
|
||||||
syncReq.Since.PresencePosition, currentPos.PresencePosition,
|
syncReq.Since.PresencePosition, rp.Notifier.CurrentPosition().PresencePosition,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -838,6 +838,8 @@ func (a *UserInternalAPI) QueryAccountByPassword(ctx context.Context, req *api.Q
|
||||||
return nil
|
return nil
|
||||||
case bcrypt.ErrMismatchedHashAndPassword: // user exists, but password doesn't match
|
case bcrypt.ErrMismatchedHashAndPassword: // user exists, but password doesn't match
|
||||||
return nil
|
return nil
|
||||||
|
case bcrypt.ErrHashTooShort: // user exists, but probably a passwordless account
|
||||||
|
return nil
|
||||||
default:
|
default:
|
||||||
res.Exists = true
|
res.Exists = true
|
||||||
res.Account = acc
|
res.Account = acc
|
||||||
|
|
|
@ -75,6 +75,9 @@ func (d *Database) GetAccountByPassword(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(hash) == 0 && len(plaintextPassword) > 0 {
|
||||||
|
return nil, bcrypt.ErrHashTooShort
|
||||||
|
}
|
||||||
if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(plaintextPassword)); err != nil {
|
if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(plaintextPassword)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,6 +151,33 @@ func TestQueryProfile(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestPasswordlessLoginFails ensures that a passwordless account cannot
|
||||||
|
// be logged into using an arbitrary password (effectively a regression test
|
||||||
|
// for https://github.com/matrix-org/dendrite/issues/2780).
|
||||||
|
func TestPasswordlessLoginFails(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
||||||
|
userAPI, accountDB, close := MustMakeInternalAPI(t, apiTestOpts{}, dbType)
|
||||||
|
defer close()
|
||||||
|
_, err := accountDB.CreateAccount(ctx, "auser", "", "", api.AccountTypeAppService)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to make account: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
userReq := &api.QueryAccountByPasswordRequest{
|
||||||
|
Localpart: "auser",
|
||||||
|
PlaintextPassword: "apassword",
|
||||||
|
}
|
||||||
|
userRes := &api.QueryAccountByPasswordResponse{}
|
||||||
|
if err := userAPI.QueryAccountByPassword(ctx, userReq, userRes); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if userRes.Exists || userRes.Account != nil {
|
||||||
|
t.Fatal("QueryAccountByPassword should not return correctly for a passwordless account")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoginToken(t *testing.T) {
|
func TestLoginToken(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue