mirror of
https://github.com/matrix-org/dendrite.git
synced 2025-12-17 03:43:11 -06:00
Merge branch 'master' into app-service-reg
This commit is contained in:
commit
758f70545d
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -43,3 +43,9 @@ _testmain.go
|
||||||
|
|
||||||
# Default configuration file
|
# Default configuration file
|
||||||
dendrite.yaml
|
dendrite.yaml
|
||||||
|
|
||||||
|
# Database files
|
||||||
|
*.db
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
*.log*
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ linters-settings:
|
||||||
#local-prefixes: github.com/org/project
|
#local-prefixes: github.com/org/project
|
||||||
gocyclo:
|
gocyclo:
|
||||||
# minimal code complexity to report, 30 by default (but we recommend 10-20)
|
# minimal code complexity to report, 30 by default (but we recommend 10-20)
|
||||||
min-complexity: 12
|
min-complexity: 13
|
||||||
maligned:
|
maligned:
|
||||||
# print struct with more effective memory layout or not, false by default
|
# print struct with more effective memory layout or not, false by default
|
||||||
suggest-new: true
|
suggest-new: true
|
||||||
|
|
|
||||||
|
|
@ -28,3 +28,4 @@ discussion should happen in
|
||||||
|
|
||||||
There's plenty still to do to make Dendrite usable! We're tracking progress in a
|
There's plenty still to do to make Dendrite usable! We're tracking progress in a
|
||||||
[project board](https://github.com/matrix-org/dendrite/projects/2).
|
[project board](https://github.com/matrix-org/dendrite/projects/2).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ Diagram:
|
||||||
| | | |
|
| | | |
|
||||||
| | +---+ | |
|
| | +---+ | |
|
||||||
| | +----------| S | | |
|
| | +----------| S | | |
|
||||||
| | | Typing +---+ | |
|
| | | EDU +---+ | |
|
||||||
| |>=========================================>| Server |>=====================>| |
|
| |>=========================================>| Server |>=====================>| |
|
||||||
+------------+ | | +----------+
|
+------------+ | | +----------+
|
||||||
+---+ | |
|
+---+ | |
|
||||||
|
|
@ -156,7 +156,7 @@ choke-point to implement ratelimiting and backoff correctly.
|
||||||
* It may be impossible to implement without folding it into the Room Server
|
* It may be impossible to implement without folding it into the Room Server
|
||||||
forever coupling the components together.
|
forever coupling the components together.
|
||||||
|
|
||||||
## Typing Server
|
## EDU Server
|
||||||
|
|
||||||
* Reads new updates to typing from the logs written by the FS and CTS.
|
* Reads new updates to typing from the logs written by the FS and CTS.
|
||||||
* Updates the current list of people typing in a room.
|
* Updates the current list of people typing in a room.
|
||||||
|
|
@ -179,7 +179,7 @@ choke-point to implement ratelimiting and backoff correctly.
|
||||||
* Reads new events and the current state of the rooms from logs written by the Room Server.
|
* Reads new events and the current state of the rooms from logs written by the Room Server.
|
||||||
* Reads new receipts positions from the logs written by the Receipts Server.
|
* Reads new receipts positions from the logs written by the Receipts Server.
|
||||||
* Reads changes to presence from the logs written by the Presence Server.
|
* Reads changes to presence from the logs written by the Presence Server.
|
||||||
* Reads changes to typing from the logs written by the Typing Server.
|
* Reads changes to typing from the logs written by the EDU Server.
|
||||||
* Writes when a client starts and stops syncing to the logs.
|
* Writes when a client starts and stops syncing to the logs.
|
||||||
|
|
||||||
## Client Search
|
## Client Search
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ package api
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
|
|
@ -97,15 +98,15 @@ type httpAppServiceQueryAPI struct {
|
||||||
|
|
||||||
// NewAppServiceQueryAPIHTTP creates a AppServiceQueryAPI implemented by talking
|
// NewAppServiceQueryAPIHTTP creates a AppServiceQueryAPI implemented by talking
|
||||||
// to a HTTP POST API.
|
// to a HTTP POST API.
|
||||||
// If httpClient is nil then it uses http.DefaultClient
|
// If httpClient is nil an error is returned
|
||||||
func NewAppServiceQueryAPIHTTP(
|
func NewAppServiceQueryAPIHTTP(
|
||||||
appserviceURL string,
|
appserviceURL string,
|
||||||
httpClient *http.Client,
|
httpClient *http.Client,
|
||||||
) AppServiceQueryAPI {
|
) (AppServiceQueryAPI, error) {
|
||||||
if httpClient == nil {
|
if httpClient == nil {
|
||||||
httpClient = http.DefaultClient
|
return nil, errors.New("NewRoomserverAliasAPIHTTP: httpClient is <nil>")
|
||||||
}
|
}
|
||||||
return &httpAppServiceQueryAPI{appserviceURL, httpClient}
|
return &httpAppServiceQueryAPI{appserviceURL, httpClient}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoomAliasExists implements AppServiceQueryAPI
|
// RoomAliasExists implements AppServiceQueryAPI
|
||||||
|
|
@ -140,7 +141,7 @@ func RetrieveUserProfile(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
userID string,
|
userID string,
|
||||||
asAPI AppServiceQueryAPI,
|
asAPI AppServiceQueryAPI,
|
||||||
accountDB *accounts.Database,
|
accountDB accounts.Database,
|
||||||
) (*authtypes.Profile, error) {
|
) (*authtypes.Profile, error) {
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,8 @@ import (
|
||||||
// component.
|
// component.
|
||||||
func SetupAppServiceAPIComponent(
|
func SetupAppServiceAPIComponent(
|
||||||
base *basecomponent.BaseDendrite,
|
base *basecomponent.BaseDendrite,
|
||||||
accountsDB *accounts.Database,
|
accountsDB accounts.Database,
|
||||||
deviceDB *devices.Database,
|
deviceDB devices.Database,
|
||||||
federation *gomatrixserverlib.FederationClient,
|
federation *gomatrixserverlib.FederationClient,
|
||||||
roomserverAliasAPI roomserverAPI.RoomserverAliasAPI,
|
roomserverAliasAPI roomserverAPI.RoomserverAliasAPI,
|
||||||
roomserverQueryAPI roomserverAPI.RoomserverQueryAPI,
|
roomserverQueryAPI roomserverAPI.RoomserverQueryAPI,
|
||||||
|
|
@ -100,7 +100,7 @@ func SetupAppServiceAPIComponent(
|
||||||
|
|
||||||
// Set up HTTP Endpoints
|
// Set up HTTP Endpoints
|
||||||
routing.Setup(
|
routing.Setup(
|
||||||
base.APIMux, *base.Cfg, roomserverQueryAPI, roomserverAliasAPI,
|
base.APIMux, base.Cfg, roomserverQueryAPI, roomserverAliasAPI,
|
||||||
accountsDB, federation, transactionsCache,
|
accountsDB, federation, transactionsCache,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -111,8 +111,8 @@ func SetupAppServiceAPIComponent(
|
||||||
// `sender_localpart` field of each application service if it doesn't
|
// `sender_localpart` field of each application service if it doesn't
|
||||||
// exist already
|
// exist already
|
||||||
func generateAppServiceAccount(
|
func generateAppServiceAccount(
|
||||||
accountsDB *accounts.Database,
|
accountsDB accounts.Database,
|
||||||
deviceDB *devices.Database,
|
deviceDB devices.Database,
|
||||||
as config.ApplicationService,
|
as config.ApplicationService,
|
||||||
) error {
|
) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,8 @@ import (
|
||||||
// OutputRoomEventConsumer consumes events that originated in the room server.
|
// OutputRoomEventConsumer consumes events that originated in the room server.
|
||||||
type OutputRoomEventConsumer struct {
|
type OutputRoomEventConsumer struct {
|
||||||
roomServerConsumer *common.ContinualConsumer
|
roomServerConsumer *common.ContinualConsumer
|
||||||
db *accounts.Database
|
db accounts.Database
|
||||||
asDB *storage.Database
|
asDB storage.Database
|
||||||
query api.RoomserverQueryAPI
|
query api.RoomserverQueryAPI
|
||||||
alias api.RoomserverAliasAPI
|
alias api.RoomserverAliasAPI
|
||||||
serverName string
|
serverName string
|
||||||
|
|
@ -46,8 +46,8 @@ type OutputRoomEventConsumer struct {
|
||||||
func NewOutputRoomEventConsumer(
|
func NewOutputRoomEventConsumer(
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
kafkaConsumer sarama.Consumer,
|
kafkaConsumer sarama.Consumer,
|
||||||
store *accounts.Database,
|
store accounts.Database,
|
||||||
appserviceDB *storage.Database,
|
appserviceDB storage.Database,
|
||||||
queryAPI api.RoomserverQueryAPI,
|
queryAPI api.RoomserverQueryAPI,
|
||||||
aliasAPI api.RoomserverAliasAPI,
|
aliasAPI api.RoomserverAliasAPI,
|
||||||
workerStates []types.ApplicationServiceWorkerState,
|
workerStates []types.ApplicationServiceWorkerState,
|
||||||
|
|
@ -114,19 +114,19 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error {
|
||||||
// lookupMissingStateEvents looks up the state events that are added by a new event,
|
// lookupMissingStateEvents looks up the state events that are added by a new event,
|
||||||
// and returns any not already present.
|
// and returns any not already present.
|
||||||
func (s *OutputRoomEventConsumer) lookupMissingStateEvents(
|
func (s *OutputRoomEventConsumer) lookupMissingStateEvents(
|
||||||
addsStateEventIDs []string, event gomatrixserverlib.Event,
|
addsStateEventIDs []string, event gomatrixserverlib.HeaderedEvent,
|
||||||
) ([]gomatrixserverlib.Event, error) {
|
) ([]gomatrixserverlib.HeaderedEvent, error) {
|
||||||
// Fast path if there aren't any new state events.
|
// Fast path if there aren't any new state events.
|
||||||
if len(addsStateEventIDs) == 0 {
|
if len(addsStateEventIDs) == 0 {
|
||||||
return []gomatrixserverlib.Event{}, nil
|
return []gomatrixserverlib.HeaderedEvent{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fast path if the only state event added is the event itself.
|
// Fast path if the only state event added is the event itself.
|
||||||
if len(addsStateEventIDs) == 1 && addsStateEventIDs[0] == event.EventID() {
|
if len(addsStateEventIDs) == 1 && addsStateEventIDs[0] == event.EventID() {
|
||||||
return []gomatrixserverlib.Event{}, nil
|
return []gomatrixserverlib.HeaderedEvent{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result := []gomatrixserverlib.Event{}
|
result := []gomatrixserverlib.HeaderedEvent{}
|
||||||
missing := []string{}
|
missing := []string{}
|
||||||
for _, id := range addsStateEventIDs {
|
for _, id := range addsStateEventIDs {
|
||||||
if id != event.EventID() {
|
if id != event.EventID() {
|
||||||
|
|
@ -155,7 +155,7 @@ func (s *OutputRoomEventConsumer) lookupMissingStateEvents(
|
||||||
// application service.
|
// application service.
|
||||||
func (s *OutputRoomEventConsumer) filterRoomserverEvents(
|
func (s *OutputRoomEventConsumer) filterRoomserverEvents(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
events []gomatrixserverlib.Event,
|
events []gomatrixserverlib.HeaderedEvent,
|
||||||
) error {
|
) error {
|
||||||
for _, ws := range s.workerStates {
|
for _, ws := range s.workerStates {
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
|
|
@ -178,7 +178,7 @@ func (s *OutputRoomEventConsumer) filterRoomserverEvents(
|
||||||
|
|
||||||
// appserviceIsInterestedInEvent returns a boolean depending on whether a given
|
// appserviceIsInterestedInEvent returns a boolean depending on whether a given
|
||||||
// event falls within one of a given application service's namespaces.
|
// event falls within one of a given application service's namespaces.
|
||||||
func (s *OutputRoomEventConsumer) appserviceIsInterestedInEvent(ctx context.Context, event gomatrixserverlib.Event, appservice config.ApplicationService) bool {
|
func (s *OutputRoomEventConsumer) appserviceIsInterestedInEvent(ctx context.Context, event gomatrixserverlib.HeaderedEvent, appservice config.ApplicationService) bool {
|
||||||
// No reason to queue events if they'll never be sent to the application
|
// No reason to queue events if they'll never be sent to the application
|
||||||
// service
|
// service
|
||||||
if appservice.URL == "" {
|
if appservice.URL == "" {
|
||||||
|
|
@ -191,6 +191,12 @@ func (s *OutputRoomEventConsumer) appserviceIsInterestedInEvent(ctx context.Cont
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if event.Type() == gomatrixserverlib.MRoomMember && event.StateKey() != nil {
|
||||||
|
if appservice.IsInterestedInUserID(*event.StateKey()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check all known room aliases of the room the event came from
|
// Check all known room aliases of the room the event came from
|
||||||
queryReq := api.GetAliasesForRoomIDRequest{RoomID: event.RoomID()}
|
queryReq := api.GetAliasesForRoomIDRequest{RoomID: event.RoomID()}
|
||||||
var queryRes api.GetAliasesForRoomIDResponse
|
var queryRes api.GetAliasesForRoomIDResponse
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,9 @@ const pathPrefixApp = "/_matrix/app/v1"
|
||||||
// applied:
|
// applied:
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
func Setup(
|
func Setup(
|
||||||
apiMux *mux.Router, cfg config.Dendrite, // nolint: unparam
|
apiMux *mux.Router, cfg *config.Dendrite, // nolint: unparam
|
||||||
queryAPI api.RoomserverQueryAPI, aliasAPI api.RoomserverAliasAPI, // nolint: unparam
|
queryAPI api.RoomserverQueryAPI, aliasAPI api.RoomserverAliasAPI, // nolint: unparam
|
||||||
accountDB *accounts.Database, // nolint: unparam
|
accountDB accounts.Database, // nolint: unparam
|
||||||
federation *gomatrixserverlib.FederationClient, // nolint: unparam
|
federation *gomatrixserverlib.FederationClient, // nolint: unparam
|
||||||
transactionsCache *transactions.Cache, // nolint: unparam
|
transactionsCache *transactions.Cache, // nolint: unparam
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
30
appservice/storage/interface.go
Normal file
30
appservice/storage/interface.go
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Database interface {
|
||||||
|
StoreEvent(ctx context.Context, appServiceID string, event *gomatrixserverlib.HeaderedEvent) error
|
||||||
|
GetEventsWithAppServiceID(ctx context.Context, appServiceID string, limit int) (int, int, []gomatrixserverlib.HeaderedEvent, bool, error)
|
||||||
|
CountEventsWithAppServiceID(ctx context.Context, appServiceID string) (int, error)
|
||||||
|
UpdateTxnIDForEvents(ctx context.Context, appserviceID string, maxID, txnID int) error
|
||||||
|
RemoveEventsBeforeAndIncludingID(ctx context.Context, appserviceID string, eventTableID int) error
|
||||||
|
GetLatestTxnID(ctx context.Context) (int, error)
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2018 New Vector Ltd
|
// Copyright 2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -32,7 +33,7 @@ CREATE TABLE IF NOT EXISTS appservice_events (
|
||||||
-- The ID of the application service the event will be sent to
|
-- The ID of the application service the event will be sent to
|
||||||
as_id TEXT NOT NULL,
|
as_id TEXT NOT NULL,
|
||||||
-- JSON representation of the event
|
-- JSON representation of the event
|
||||||
event_json TEXT NOT NULL,
|
headered_event_json TEXT NOT NULL,
|
||||||
-- The ID of the transaction that this event is a part of
|
-- The ID of the transaction that this event is a part of
|
||||||
txn_id BIGINT NOT NULL
|
txn_id BIGINT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
@ -41,14 +42,14 @@ CREATE INDEX IF NOT EXISTS appservice_events_as_id ON appservice_events(as_id);
|
||||||
`
|
`
|
||||||
|
|
||||||
const selectEventsByApplicationServiceIDSQL = "" +
|
const selectEventsByApplicationServiceIDSQL = "" +
|
||||||
"SELECT id, event_json, txn_id " +
|
"SELECT id, headered_event_json, txn_id " +
|
||||||
"FROM appservice_events WHERE as_id = $1 ORDER BY txn_id DESC, id ASC"
|
"FROM appservice_events WHERE as_id = $1 ORDER BY txn_id DESC, id ASC"
|
||||||
|
|
||||||
const countEventsByApplicationServiceIDSQL = "" +
|
const countEventsByApplicationServiceIDSQL = "" +
|
||||||
"SELECT COUNT(id) FROM appservice_events WHERE as_id = $1"
|
"SELECT COUNT(id) FROM appservice_events WHERE as_id = $1"
|
||||||
|
|
||||||
const insertEventSQL = "" +
|
const insertEventSQL = "" +
|
||||||
"INSERT INTO appservice_events(as_id, event_json, txn_id) " +
|
"INSERT INTO appservice_events(as_id, headered_event_json, txn_id) " +
|
||||||
"VALUES ($1, $2, $3)"
|
"VALUES ($1, $2, $3)"
|
||||||
|
|
||||||
const updateTxnIDForEventsSQL = "" +
|
const updateTxnIDForEventsSQL = "" +
|
||||||
|
|
@ -106,7 +107,7 @@ func (s *eventsStatements) selectEventsByApplicationServiceID(
|
||||||
limit int,
|
limit int,
|
||||||
) (
|
) (
|
||||||
txnID, maxID int,
|
txnID, maxID int,
|
||||||
events []gomatrixserverlib.Event,
|
events []gomatrixserverlib.HeaderedEvent,
|
||||||
eventsRemaining bool,
|
eventsRemaining bool,
|
||||||
err error,
|
err error,
|
||||||
) {
|
) {
|
||||||
|
|
@ -131,7 +132,7 @@ func (s *eventsStatements) selectEventsByApplicationServiceID(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func retrieveEvents(eventRows *sql.Rows, limit int) (events []gomatrixserverlib.Event, maxID, txnID int, eventsRemaining bool, err error) {
|
func retrieveEvents(eventRows *sql.Rows, limit int) (events []gomatrixserverlib.HeaderedEvent, maxID, txnID int, eventsRemaining bool, err error) {
|
||||||
// Get current time for use in calculating event age
|
// Get current time for use in calculating event age
|
||||||
nowMilli := time.Now().UnixNano() / int64(time.Millisecond)
|
nowMilli := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
|
||||||
|
|
@ -140,7 +141,7 @@ func retrieveEvents(eventRows *sql.Rows, limit int) (events []gomatrixserverlib.
|
||||||
// new ones. Send back those events first.
|
// new ones. Send back those events first.
|
||||||
lastTxnID := invalidTxnID
|
lastTxnID := invalidTxnID
|
||||||
for eventsProcessed := 0; eventRows.Next(); {
|
for eventsProcessed := 0; eventRows.Next(); {
|
||||||
var event gomatrixserverlib.Event
|
var event gomatrixserverlib.HeaderedEvent
|
||||||
var eventJSON []byte
|
var eventJSON []byte
|
||||||
var id int
|
var id int
|
||||||
err = eventRows.Scan(
|
err = eventRows.Scan(
|
||||||
|
|
@ -208,7 +209,7 @@ func (s *eventsStatements) countEventsByApplicationServiceID(
|
||||||
func (s *eventsStatements) insertEvent(
|
func (s *eventsStatements) insertEvent(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
appServiceID string,
|
appServiceID string,
|
||||||
event *gomatrixserverlib.Event,
|
event *gomatrixserverlib.HeaderedEvent,
|
||||||
) (err error) {
|
) (err error) {
|
||||||
// Convert event to JSON before inserting
|
// Convert event to JSON before inserting
|
||||||
eventJSON, err := json.Marshal(event)
|
eventJSON, err := json.Marshal(event)
|
||||||
111
appservice/storage/postgres/storage.go
Normal file
111
appservice/storage/postgres/storage.go
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
// Copyright 2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
// Import postgres database driver
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Database stores events intended to be later sent to application services
|
||||||
|
type Database struct {
|
||||||
|
events eventsStatements
|
||||||
|
txnID txnStatements
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDatabase opens a new database
|
||||||
|
func NewDatabase(dataSourceName string) (*Database, error) {
|
||||||
|
var result Database
|
||||||
|
var err error
|
||||||
|
if result.db, err = sql.Open("postgres", dataSourceName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = result.prepare(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) prepare() error {
|
||||||
|
if err := d.events.prepare(d.db); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.txnID.prepare(d.db)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreEvent takes in a gomatrixserverlib.HeaderedEvent and stores it in the database
|
||||||
|
// for a transaction worker to pull and later send to an application service.
|
||||||
|
func (d *Database) StoreEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
appServiceID string,
|
||||||
|
event *gomatrixserverlib.HeaderedEvent,
|
||||||
|
) error {
|
||||||
|
return d.events.insertEvent(ctx, appServiceID, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEventsWithAppServiceID returns a slice of events and their IDs intended to
|
||||||
|
// be sent to an application service given its ID.
|
||||||
|
func (d *Database) GetEventsWithAppServiceID(
|
||||||
|
ctx context.Context,
|
||||||
|
appServiceID string,
|
||||||
|
limit int,
|
||||||
|
) (int, int, []gomatrixserverlib.HeaderedEvent, bool, error) {
|
||||||
|
return d.events.selectEventsByApplicationServiceID(ctx, appServiceID, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountEventsWithAppServiceID returns the number of events destined for an
|
||||||
|
// application service given its ID.
|
||||||
|
func (d *Database) CountEventsWithAppServiceID(
|
||||||
|
ctx context.Context,
|
||||||
|
appServiceID string,
|
||||||
|
) (int, error) {
|
||||||
|
return d.events.countEventsByApplicationServiceID(ctx, appServiceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTxnIDForEvents takes in an application service ID and a
|
||||||
|
// and stores them in the DB, unless the pair already exists, in
|
||||||
|
// which case it updates them.
|
||||||
|
func (d *Database) UpdateTxnIDForEvents(
|
||||||
|
ctx context.Context,
|
||||||
|
appserviceID string,
|
||||||
|
maxID, txnID int,
|
||||||
|
) error {
|
||||||
|
return d.events.updateTxnIDForEvents(ctx, appserviceID, maxID, txnID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveEventsBeforeAndIncludingID removes all events from the database that
|
||||||
|
// are less than or equal to a given maximum ID. IDs here are implemented as a
|
||||||
|
// serial, thus this should always delete events in chronological order.
|
||||||
|
func (d *Database) RemoveEventsBeforeAndIncludingID(
|
||||||
|
ctx context.Context,
|
||||||
|
appserviceID string,
|
||||||
|
eventTableID int,
|
||||||
|
) error {
|
||||||
|
return d.events.deleteEventsBeforeAndIncludingID(ctx, appserviceID, eventTableID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLatestTxnID returns the latest available transaction id
|
||||||
|
func (d *Database) GetLatestTxnID(
|
||||||
|
ctx context.Context,
|
||||||
|
) (int, error) {
|
||||||
|
return d.txnID.selectTxnID(ctx)
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2018 New Vector Ltd
|
// Copyright 2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
249
appservice/storage/sqlite3/appservice_events_table.go
Normal file
249
appservice/storage/sqlite3/appservice_events_table.go
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
// Copyright 2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const appserviceEventsSchema = `
|
||||||
|
-- Stores events to be sent to application services
|
||||||
|
CREATE TABLE IF NOT EXISTS appservice_events (
|
||||||
|
-- An auto-incrementing id unique to each event in the table
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
-- The ID of the application service the event will be sent to
|
||||||
|
as_id TEXT NOT NULL,
|
||||||
|
-- JSON representation of the event
|
||||||
|
headered_event_json TEXT NOT NULL,
|
||||||
|
-- The ID of the transaction that this event is a part of
|
||||||
|
txn_id INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS appservice_events_as_id ON appservice_events(as_id);
|
||||||
|
`
|
||||||
|
|
||||||
|
const selectEventsByApplicationServiceIDSQL = "" +
|
||||||
|
"SELECT id, headered_event_json, txn_id " +
|
||||||
|
"FROM appservice_events WHERE as_id = $1 ORDER BY txn_id DESC, id ASC"
|
||||||
|
|
||||||
|
const countEventsByApplicationServiceIDSQL = "" +
|
||||||
|
"SELECT COUNT(id) FROM appservice_events WHERE as_id = $1"
|
||||||
|
|
||||||
|
const insertEventSQL = "" +
|
||||||
|
"INSERT INTO appservice_events(as_id, headered_event_json, txn_id) " +
|
||||||
|
"VALUES ($1, $2, $3)"
|
||||||
|
|
||||||
|
const updateTxnIDForEventsSQL = "" +
|
||||||
|
"UPDATE appservice_events SET txn_id = $1 WHERE as_id = $2 AND id <= $3"
|
||||||
|
|
||||||
|
const deleteEventsBeforeAndIncludingIDSQL = "" +
|
||||||
|
"DELETE FROM appservice_events WHERE as_id = $1 AND id <= $2"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// A transaction ID number that no transaction should ever have. Used for
|
||||||
|
// checking again the default value.
|
||||||
|
invalidTxnID = -2
|
||||||
|
)
|
||||||
|
|
||||||
|
type eventsStatements struct {
|
||||||
|
selectEventsByApplicationServiceIDStmt *sql.Stmt
|
||||||
|
countEventsByApplicationServiceIDStmt *sql.Stmt
|
||||||
|
insertEventStmt *sql.Stmt
|
||||||
|
updateTxnIDForEventsStmt *sql.Stmt
|
||||||
|
deleteEventsBeforeAndIncludingIDStmt *sql.Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *eventsStatements) prepare(db *sql.DB) (err error) {
|
||||||
|
_, err = db.Exec(appserviceEventsSchema)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.selectEventsByApplicationServiceIDStmt, err = db.Prepare(selectEventsByApplicationServiceIDSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.countEventsByApplicationServiceIDStmt, err = db.Prepare(countEventsByApplicationServiceIDSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.insertEventStmt, err = db.Prepare(insertEventSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.updateTxnIDForEventsStmt, err = db.Prepare(updateTxnIDForEventsSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.deleteEventsBeforeAndIncludingIDStmt, err = db.Prepare(deleteEventsBeforeAndIncludingIDSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// selectEventsByApplicationServiceID takes in an application service ID and
|
||||||
|
// returns a slice of events that need to be sent to that application service,
|
||||||
|
// as well as an int later used to remove these same events from the database
|
||||||
|
// once successfully sent to an application service.
|
||||||
|
func (s *eventsStatements) selectEventsByApplicationServiceID(
|
||||||
|
ctx context.Context,
|
||||||
|
applicationServiceID string,
|
||||||
|
limit int,
|
||||||
|
) (
|
||||||
|
txnID, maxID int,
|
||||||
|
events []gomatrixserverlib.HeaderedEvent,
|
||||||
|
eventsRemaining bool,
|
||||||
|
err error,
|
||||||
|
) {
|
||||||
|
// Retrieve events from the database. Unsuccessfully sent events first
|
||||||
|
eventRows, err := s.selectEventsByApplicationServiceIDStmt.QueryContext(ctx, applicationServiceID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err = eventRows.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"appservice": applicationServiceID,
|
||||||
|
}).WithError(err).Fatalf("appservice unable to select new events to send")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
events, maxID, txnID, eventsRemaining, err = retrieveEvents(eventRows, limit)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieveEvents(eventRows *sql.Rows, limit int) (events []gomatrixserverlib.HeaderedEvent, maxID, txnID int, eventsRemaining bool, err error) {
|
||||||
|
// Get current time for use in calculating event age
|
||||||
|
nowMilli := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
|
||||||
|
// Iterate through each row and store event contents
|
||||||
|
// If txn_id changes dramatically, we've switched from collecting old events to
|
||||||
|
// new ones. Send back those events first.
|
||||||
|
lastTxnID := invalidTxnID
|
||||||
|
for eventsProcessed := 0; eventRows.Next(); {
|
||||||
|
var event gomatrixserverlib.HeaderedEvent
|
||||||
|
var eventJSON []byte
|
||||||
|
var id int
|
||||||
|
err = eventRows.Scan(
|
||||||
|
&id,
|
||||||
|
&eventJSON,
|
||||||
|
&txnID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, 0, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal eventJSON
|
||||||
|
if err = json.Unmarshal(eventJSON, &event); err != nil {
|
||||||
|
return nil, 0, 0, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If txnID has changed on this event from the previous event, then we've
|
||||||
|
// reached the end of a transaction's events. Return only those events.
|
||||||
|
if lastTxnID > invalidTxnID && lastTxnID != txnID {
|
||||||
|
return events, maxID, lastTxnID, true, nil
|
||||||
|
}
|
||||||
|
lastTxnID = txnID
|
||||||
|
|
||||||
|
// Limit events that aren't part of an old transaction
|
||||||
|
if txnID == -1 {
|
||||||
|
// Return if we've hit the limit
|
||||||
|
if eventsProcessed++; eventsProcessed > limit {
|
||||||
|
return events, maxID, lastTxnID, true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if id > maxID {
|
||||||
|
maxID = id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Portion of the event that is unsigned due to rapid change
|
||||||
|
// TODO: Consider removing age as not many app services use it
|
||||||
|
if err = event.SetUnsignedField("age", nowMilli-int64(event.OriginServerTS())); err != nil {
|
||||||
|
return nil, 0, 0, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
events = append(events, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// countEventsByApplicationServiceID inserts an event mapped to its corresponding application service
|
||||||
|
// IDs into the db.
|
||||||
|
func (s *eventsStatements) countEventsByApplicationServiceID(
|
||||||
|
ctx context.Context,
|
||||||
|
appServiceID string,
|
||||||
|
) (int, error) {
|
||||||
|
var count int
|
||||||
|
err := s.countEventsByApplicationServiceIDStmt.QueryRowContext(ctx, appServiceID).Scan(&count)
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertEvent inserts an event mapped to its corresponding application service
|
||||||
|
// IDs into the db.
|
||||||
|
func (s *eventsStatements) insertEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
appServiceID string,
|
||||||
|
event *gomatrixserverlib.HeaderedEvent,
|
||||||
|
) (err error) {
|
||||||
|
// Convert event to JSON before inserting
|
||||||
|
eventJSON, err := json.Marshal(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.insertEventStmt.ExecContext(
|
||||||
|
ctx,
|
||||||
|
appServiceID,
|
||||||
|
eventJSON,
|
||||||
|
-1, // No transaction ID yet
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateTxnIDForEvents sets the transactionID for a collection of events. Done
|
||||||
|
// before sending them to an AppService. Referenced before sending to make sure
|
||||||
|
// we aren't constructing multiple transactions with the same events.
|
||||||
|
func (s *eventsStatements) updateTxnIDForEvents(
|
||||||
|
ctx context.Context,
|
||||||
|
appserviceID string,
|
||||||
|
maxID, txnID int,
|
||||||
|
) (err error) {
|
||||||
|
_, err = s.updateTxnIDForEventsStmt.ExecContext(ctx, txnID, appserviceID, maxID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteEventsBeforeAndIncludingID removes events matching given IDs from the database.
|
||||||
|
func (s *eventsStatements) deleteEventsBeforeAndIncludingID(
|
||||||
|
ctx context.Context,
|
||||||
|
appserviceID string,
|
||||||
|
eventTableID int,
|
||||||
|
) (err error) {
|
||||||
|
_, err = s.deleteEventsBeforeAndIncludingIDStmt.ExecContext(ctx, appserviceID, eventTableID)
|
||||||
|
return
|
||||||
|
}
|
||||||
112
appservice/storage/sqlite3/storage.go
Normal file
112
appservice/storage/sqlite3/storage.go
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
// Copyright 2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
// Import SQLite database driver
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Database stores events intended to be later sent to application services
|
||||||
|
type Database struct {
|
||||||
|
events eventsStatements
|
||||||
|
txnID txnStatements
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDatabase opens a new database
|
||||||
|
func NewDatabase(dataSourceName string) (*Database, error) {
|
||||||
|
var result Database
|
||||||
|
var err error
|
||||||
|
if result.db, err = sql.Open(common.SQLiteDriverName(), dataSourceName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = result.prepare(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) prepare() error {
|
||||||
|
if err := d.events.prepare(d.db); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.txnID.prepare(d.db)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreEvent takes in a gomatrixserverlib.HeaderedEvent and stores it in the database
|
||||||
|
// for a transaction worker to pull and later send to an application service.
|
||||||
|
func (d *Database) StoreEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
appServiceID string,
|
||||||
|
event *gomatrixserverlib.HeaderedEvent,
|
||||||
|
) error {
|
||||||
|
return d.events.insertEvent(ctx, appServiceID, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEventsWithAppServiceID returns a slice of events and their IDs intended to
|
||||||
|
// be sent to an application service given its ID.
|
||||||
|
func (d *Database) GetEventsWithAppServiceID(
|
||||||
|
ctx context.Context,
|
||||||
|
appServiceID string,
|
||||||
|
limit int,
|
||||||
|
) (int, int, []gomatrixserverlib.HeaderedEvent, bool, error) {
|
||||||
|
return d.events.selectEventsByApplicationServiceID(ctx, appServiceID, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountEventsWithAppServiceID returns the number of events destined for an
|
||||||
|
// application service given its ID.
|
||||||
|
func (d *Database) CountEventsWithAppServiceID(
|
||||||
|
ctx context.Context,
|
||||||
|
appServiceID string,
|
||||||
|
) (int, error) {
|
||||||
|
return d.events.countEventsByApplicationServiceID(ctx, appServiceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTxnIDForEvents takes in an application service ID and a
|
||||||
|
// and stores them in the DB, unless the pair already exists, in
|
||||||
|
// which case it updates them.
|
||||||
|
func (d *Database) UpdateTxnIDForEvents(
|
||||||
|
ctx context.Context,
|
||||||
|
appserviceID string,
|
||||||
|
maxID, txnID int,
|
||||||
|
) error {
|
||||||
|
return d.events.updateTxnIDForEvents(ctx, appserviceID, maxID, txnID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveEventsBeforeAndIncludingID removes all events from the database that
|
||||||
|
// are less than or equal to a given maximum ID. IDs here are implemented as a
|
||||||
|
// serial, thus this should always delete events in chronological order.
|
||||||
|
func (d *Database) RemoveEventsBeforeAndIncludingID(
|
||||||
|
ctx context.Context,
|
||||||
|
appserviceID string,
|
||||||
|
eventTableID int,
|
||||||
|
) error {
|
||||||
|
return d.events.deleteEventsBeforeAndIncludingID(ctx, appserviceID, eventTableID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLatestTxnID returns the latest available transaction id
|
||||||
|
func (d *Database) GetLatestTxnID(
|
||||||
|
ctx context.Context,
|
||||||
|
) (int, error) {
|
||||||
|
return d.txnID.selectTxnID(ctx)
|
||||||
|
}
|
||||||
60
appservice/storage/sqlite3/txn_id_counter_table.go
Normal file
60
appservice/storage/sqlite3/txn_id_counter_table.go
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright 2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
const txnIDSchema = `
|
||||||
|
-- Keeps a count of the current transaction ID
|
||||||
|
CREATE TABLE IF NOT EXISTS appservice_counters (
|
||||||
|
name TEXT PRIMARY KEY NOT NULL,
|
||||||
|
last_id INTEGER DEFAULT 1
|
||||||
|
);
|
||||||
|
INSERT OR IGNORE INTO appservice_counters (name, last_id) VALUES('txn_id', 1);
|
||||||
|
`
|
||||||
|
|
||||||
|
const selectTxnIDSQL = `
|
||||||
|
SELECT last_id FROM appservice_counters WHERE name='txn_id';
|
||||||
|
UPDATE appservice_counters SET last_id=last_id+1 WHERE name='txn_id';
|
||||||
|
`
|
||||||
|
|
||||||
|
type txnStatements struct {
|
||||||
|
selectTxnIDStmt *sql.Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *txnStatements) prepare(db *sql.DB) (err error) {
|
||||||
|
_, err = db.Exec(txnIDSchema)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.selectTxnIDStmt, err = db.Prepare(selectTxnIDSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// selectTxnID selects the latest ascending transaction ID
|
||||||
|
func (s *txnStatements) selectTxnID(
|
||||||
|
ctx context.Context,
|
||||||
|
) (txnID int, err error) {
|
||||||
|
err = s.selectTxnIDStmt.QueryRowContext(ctx).Scan(&txnID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2018 New Vector Ltd
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,99 +12,28 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build !wasm
|
||||||
|
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"net/url"
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
// Import postgres database driver
|
"github.com/matrix-org/dendrite/appservice/storage/postgres"
|
||||||
_ "github.com/lib/pq"
|
"github.com/matrix-org/dendrite/appservice/storage/sqlite3"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Database stores events intended to be later sent to application services
|
func NewDatabase(dataSourceName string) (Database, error) {
|
||||||
type Database struct {
|
uri, err := url.Parse(dataSourceName)
|
||||||
events eventsStatements
|
if err != nil {
|
||||||
txnID txnStatements
|
return postgres.NewDatabase(dataSourceName)
|
||||||
db *sql.DB
|
|
||||||
}
|
}
|
||||||
|
switch uri.Scheme {
|
||||||
// NewDatabase opens a new database
|
case "postgres":
|
||||||
func NewDatabase(dataSourceName string) (*Database, error) {
|
return postgres.NewDatabase(dataSourceName)
|
||||||
var result Database
|
case "file":
|
||||||
var err error
|
return sqlite3.NewDatabase(dataSourceName)
|
||||||
if result.db, err = sql.Open("postgres", dataSourceName); err != nil {
|
default:
|
||||||
return nil, err
|
return postgres.NewDatabase(dataSourceName)
|
||||||
}
|
}
|
||||||
if err = result.prepare(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Database) prepare() error {
|
|
||||||
if err := d.events.prepare(d.db); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.txnID.prepare(d.db)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreEvent takes in a gomatrixserverlib.Event and stores it in the database
|
|
||||||
// for a transaction worker to pull and later send to an application service.
|
|
||||||
func (d *Database) StoreEvent(
|
|
||||||
ctx context.Context,
|
|
||||||
appServiceID string,
|
|
||||||
event *gomatrixserverlib.Event,
|
|
||||||
) error {
|
|
||||||
return d.events.insertEvent(ctx, appServiceID, event)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetEventsWithAppServiceID returns a slice of events and their IDs intended to
|
|
||||||
// be sent to an application service given its ID.
|
|
||||||
func (d *Database) GetEventsWithAppServiceID(
|
|
||||||
ctx context.Context,
|
|
||||||
appServiceID string,
|
|
||||||
limit int,
|
|
||||||
) (int, int, []gomatrixserverlib.Event, bool, error) {
|
|
||||||
return d.events.selectEventsByApplicationServiceID(ctx, appServiceID, limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CountEventsWithAppServiceID returns the number of events destined for an
|
|
||||||
// application service given its ID.
|
|
||||||
func (d *Database) CountEventsWithAppServiceID(
|
|
||||||
ctx context.Context,
|
|
||||||
appServiceID string,
|
|
||||||
) (int, error) {
|
|
||||||
return d.events.countEventsByApplicationServiceID(ctx, appServiceID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateTxnIDForEvents takes in an application service ID and a
|
|
||||||
// and stores them in the DB, unless the pair already exists, in
|
|
||||||
// which case it updates them.
|
|
||||||
func (d *Database) UpdateTxnIDForEvents(
|
|
||||||
ctx context.Context,
|
|
||||||
appserviceID string,
|
|
||||||
maxID, txnID int,
|
|
||||||
) error {
|
|
||||||
return d.events.updateTxnIDForEvents(ctx, appserviceID, maxID, txnID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveEventsBeforeAndIncludingID removes all events from the database that
|
|
||||||
// are less than or equal to a given maximum ID. IDs here are implemented as a
|
|
||||||
// serial, thus this should always delete events in chronological order.
|
|
||||||
func (d *Database) RemoveEventsBeforeAndIncludingID(
|
|
||||||
ctx context.Context,
|
|
||||||
appserviceID string,
|
|
||||||
eventTableID int,
|
|
||||||
) error {
|
|
||||||
return d.events.deleteEventsBeforeAndIncludingID(ctx, appserviceID, eventTableID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLatestTxnID returns the latest available transaction id
|
|
||||||
func (d *Database) GetLatestTxnID(
|
|
||||||
ctx context.Context,
|
|
||||||
) (int, error) {
|
|
||||||
return d.txnID.selectTxnID(ctx)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
37
appservice/storage/storage_wasm.go
Normal file
37
appservice/storage/storage_wasm.go
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/appservice/storage/sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewDatabase(dataSourceName string) (Database, error) {
|
||||||
|
uri, err := url.Parse(dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Cannot use postgres implementation")
|
||||||
|
}
|
||||||
|
switch uri.Scheme {
|
||||||
|
case "postgres":
|
||||||
|
return nil, fmt.Errorf("Cannot use postgres implementation")
|
||||||
|
case "file":
|
||||||
|
return sqlite3.NewDatabase(dataSourceName)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Cannot use postgres implementation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -43,7 +43,7 @@ var (
|
||||||
// size), then send that off to the AS's /transactions/{txnID} endpoint. It also
|
// size), then send that off to the AS's /transactions/{txnID} endpoint. It also
|
||||||
// handles exponentially backing off in case the AS isn't currently available.
|
// handles exponentially backing off in case the AS isn't currently available.
|
||||||
func SetupTransactionWorkers(
|
func SetupTransactionWorkers(
|
||||||
appserviceDB *storage.Database,
|
appserviceDB storage.Database,
|
||||||
workerStates []types.ApplicationServiceWorkerState,
|
workerStates []types.ApplicationServiceWorkerState,
|
||||||
) error {
|
) error {
|
||||||
// Create a worker that handles transmitting events to a single homeserver
|
// Create a worker that handles transmitting events to a single homeserver
|
||||||
|
|
@ -58,7 +58,7 @@ func SetupTransactionWorkers(
|
||||||
|
|
||||||
// worker is a goroutine that sends any queued events to the application service
|
// worker is a goroutine that sends any queued events to the application service
|
||||||
// it is given.
|
// it is given.
|
||||||
func worker(db *storage.Database, ws types.ApplicationServiceWorkerState) {
|
func worker(db storage.Database, ws types.ApplicationServiceWorkerState) {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"appservice": ws.AppService.ID,
|
"appservice": ws.AppService.ID,
|
||||||
}).Info("starting application service")
|
}).Info("starting application service")
|
||||||
|
|
@ -149,7 +149,7 @@ func backoff(ws *types.ApplicationServiceWorkerState, err error) {
|
||||||
// transaction, and JSON-encodes the results.
|
// transaction, and JSON-encodes the results.
|
||||||
func createTransaction(
|
func createTransaction(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
db *storage.Database,
|
db storage.Database,
|
||||||
appserviceID string,
|
appserviceID string,
|
||||||
) (
|
) (
|
||||||
transactionJSON []byte,
|
transactionJSON []byte,
|
||||||
|
|
@ -181,9 +181,14 @@ func createTransaction(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ev []gomatrixserverlib.Event
|
||||||
|
for _, e := range events {
|
||||||
|
ev = append(ev, e.Event)
|
||||||
|
}
|
||||||
|
|
||||||
// Create a transaction and store the events inside
|
// Create a transaction and store the events inside
|
||||||
transaction := gomatrixserverlib.ApplicationServiceTransaction{
|
transaction := gomatrixserverlib.ApplicationServiceTransaction{
|
||||||
Events: events,
|
Events: ev,
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionJSON, err = json.Marshal(transaction)
|
transactionJSON, err = json.Marshal(transaction)
|
||||||
|
|
|
||||||
7
build.sh
7
build.sh
|
|
@ -1,3 +1,6 @@
|
||||||
#!/bin/sh
|
#!/bin/bash -eu
|
||||||
|
|
||||||
GOBIN=$PWD/`dirname $0`/bin go install -v $PWD/`dirname $0`/cmd/...
|
# Put installed packages into ./bin
|
||||||
|
export GOBIN=$PWD/`dirname $0`/bin
|
||||||
|
|
||||||
|
go install -v $PWD/`dirname $0`/cmd/...
|
||||||
|
|
@ -26,7 +26,6 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/appservice/types"
|
"github.com/matrix-org/dendrite/appservice/types"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/clientapi/userutil"
|
"github.com/matrix-org/dendrite/clientapi/userutil"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
|
|
@ -166,7 +165,8 @@ func verifyAccessToken(req *http.Request, deviceDB DeviceDatabase) (device *auth
|
||||||
JSON: jsonerror.UnknownToken("Unknown token"),
|
JSON: jsonerror.UnknownToken("Unknown token"),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
jsonErr := httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("deviceDB.GetDeviceByAccessToken failed")
|
||||||
|
jsonErr := jsonerror.InternalServerError()
|
||||||
resErr = &jsonErr
|
resErr = &jsonErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,4 +26,5 @@ type Device struct {
|
||||||
// associated with access tokens.
|
// associated with access tokens.
|
||||||
SessionID int64
|
SessionID int64
|
||||||
// TODO: display name, last used timestamp, keys, etc
|
// TODO: display name, last used timestamp, keys, etc
|
||||||
|
DisplayName string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
54
clientapi/auth/storage/accounts/interface.go
Normal file
54
clientapi/auth/storage/accounts/interface.go
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package accounts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Database interface {
|
||||||
|
common.PartitionStorer
|
||||||
|
GetAccountByPassword(ctx context.Context, localpart, plaintextPassword string) (*authtypes.Account, error)
|
||||||
|
GetProfileByLocalpart(ctx context.Context, localpart string) (*authtypes.Profile, error)
|
||||||
|
SetAvatarURL(ctx context.Context, localpart string, avatarURL string) error
|
||||||
|
SetDisplayName(ctx context.Context, localpart string, displayName string) error
|
||||||
|
CreateAccount(ctx context.Context, localpart, plaintextPassword, appserviceID string) (*authtypes.Account, error)
|
||||||
|
CreateGuestAccount(ctx context.Context) (*authtypes.Account, error)
|
||||||
|
UpdateMemberships(ctx context.Context, eventsToAdd []gomatrixserverlib.Event, idsToRemove []string) error
|
||||||
|
GetMembershipInRoomByLocalpart(ctx context.Context, localpart, roomID string) (authtypes.Membership, error)
|
||||||
|
GetRoomIDsByLocalPart(ctx context.Context, localpart string) ([]string, error)
|
||||||
|
GetMembershipsByLocalpart(ctx context.Context, localpart string) (memberships []authtypes.Membership, err error)
|
||||||
|
SaveAccountData(ctx context.Context, localpart, roomID, dataType, content string) error
|
||||||
|
GetAccountData(ctx context.Context, localpart string) (global []gomatrixserverlib.ClientEvent, rooms map[string][]gomatrixserverlib.ClientEvent, err error)
|
||||||
|
GetAccountDataByType(ctx context.Context, localpart, roomID, dataType string) (data *gomatrixserverlib.ClientEvent, err error)
|
||||||
|
GetNewNumericLocalpart(ctx context.Context) (int64, error)
|
||||||
|
SaveThreePIDAssociation(ctx context.Context, threepid, localpart, medium string) (err error)
|
||||||
|
RemoveThreePIDAssociation(ctx context.Context, threepid string, medium string) (err error)
|
||||||
|
GetLocalpartForThreePID(ctx context.Context, threepid string, medium string) (localpart string, err error)
|
||||||
|
GetThreePIDsForLocalpart(ctx context.Context, localpart string) (threepids []authtypes.ThreePID, err error)
|
||||||
|
GetFilter(ctx context.Context, localpart string, filterID string) (*gomatrixserverlib.Filter, error)
|
||||||
|
PutFilter(ctx context.Context, localpart string, filter *gomatrixserverlib.Filter) (string, error)
|
||||||
|
CheckAccountAvailability(ctx context.Context, localpart string) (bool, error)
|
||||||
|
GetAccountByLocalpart(ctx context.Context, localpart string) (*authtypes.Account, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err3PIDInUse is the error returned when trying to save an association involving
|
||||||
|
// a third-party identifier which is already associated to a local user.
|
||||||
|
var Err3PIDInUse = errors.New("This third-party identifier is already in use")
|
||||||
143
clientapi/auth/storage/accounts/postgres/account_data_table.go
Normal file
143
clientapi/auth/storage/accounts/postgres/account_data_table.go
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
const accountDataSchema = `
|
||||||
|
-- Stores data about accounts data.
|
||||||
|
CREATE TABLE IF NOT EXISTS account_data (
|
||||||
|
-- The Matrix user ID localpart for this account
|
||||||
|
localpart TEXT NOT NULL,
|
||||||
|
-- The room ID for this data (empty string if not specific to a room)
|
||||||
|
room_id TEXT,
|
||||||
|
-- The account data type
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
-- The account data content
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY(localpart, room_id, type)
|
||||||
|
);
|
||||||
|
`
|
||||||
|
|
||||||
|
const insertAccountDataSQL = `
|
||||||
|
INSERT INTO account_data(localpart, room_id, type, content) VALUES($1, $2, $3, $4)
|
||||||
|
ON CONFLICT (localpart, room_id, type) DO UPDATE SET content = EXCLUDED.content
|
||||||
|
`
|
||||||
|
|
||||||
|
const selectAccountDataSQL = "" +
|
||||||
|
"SELECT room_id, type, content FROM account_data WHERE localpart = $1"
|
||||||
|
|
||||||
|
const selectAccountDataByTypeSQL = "" +
|
||||||
|
"SELECT content FROM account_data WHERE localpart = $1 AND room_id = $2 AND type = $3"
|
||||||
|
|
||||||
|
type accountDataStatements struct {
|
||||||
|
insertAccountDataStmt *sql.Stmt
|
||||||
|
selectAccountDataStmt *sql.Stmt
|
||||||
|
selectAccountDataByTypeStmt *sql.Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *accountDataStatements) prepare(db *sql.DB) (err error) {
|
||||||
|
_, err = db.Exec(accountDataSchema)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.insertAccountDataStmt, err = db.Prepare(insertAccountDataSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.selectAccountDataStmt, err = db.Prepare(selectAccountDataSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.selectAccountDataByTypeStmt, err = db.Prepare(selectAccountDataByTypeSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *accountDataStatements) insertAccountData(
|
||||||
|
ctx context.Context, txn *sql.Tx, localpart, roomID, dataType, content string,
|
||||||
|
) (err error) {
|
||||||
|
stmt := txn.Stmt(s.insertAccountDataStmt)
|
||||||
|
_, err = stmt.ExecContext(ctx, localpart, roomID, dataType, content)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *accountDataStatements) selectAccountData(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) (
|
||||||
|
global []gomatrixserverlib.ClientEvent,
|
||||||
|
rooms map[string][]gomatrixserverlib.ClientEvent,
|
||||||
|
err error,
|
||||||
|
) {
|
||||||
|
rows, err := s.selectAccountDataStmt.QueryContext(ctx, localpart)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer common.CloseAndLogIfError(ctx, rows, "selectAccountData: rows.close() failed")
|
||||||
|
|
||||||
|
global = []gomatrixserverlib.ClientEvent{}
|
||||||
|
rooms = make(map[string][]gomatrixserverlib.ClientEvent)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var roomID string
|
||||||
|
var dataType string
|
||||||
|
var content []byte
|
||||||
|
|
||||||
|
if err = rows.Scan(&roomID, &dataType, &content); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ac := gomatrixserverlib.ClientEvent{
|
||||||
|
Type: dataType,
|
||||||
|
Content: content,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(roomID) > 0 {
|
||||||
|
rooms[roomID] = append(rooms[roomID], ac)
|
||||||
|
} else {
|
||||||
|
global = append(global, ac)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return global, rooms, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *accountDataStatements) selectAccountDataByType(
|
||||||
|
ctx context.Context, localpart, roomID, dataType string,
|
||||||
|
) (data *gomatrixserverlib.ClientEvent, err error) {
|
||||||
|
stmt := s.selectAccountDataByTypeStmt
|
||||||
|
var content []byte
|
||||||
|
|
||||||
|
if err = stmt.QueryRowContext(ctx, localpart, roomID, dataType).Scan(&content); err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data = &gomatrixserverlib.ClientEvent{
|
||||||
|
Type: dataType,
|
||||||
|
Content: content,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package accounts
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -91,10 +91,10 @@ func (s *accountsStatements) prepare(db *sql.DB, server gomatrixserverlib.Server
|
||||||
// this account will be passwordless. Returns an error if this account already exists. Returns the account
|
// this account will be passwordless. Returns an error if this account already exists. Returns the account
|
||||||
// on success.
|
// on success.
|
||||||
func (s *accountsStatements) insertAccount(
|
func (s *accountsStatements) insertAccount(
|
||||||
ctx context.Context, localpart, hash, appserviceID string,
|
ctx context.Context, txn *sql.Tx, localpart, hash, appserviceID string,
|
||||||
) (*authtypes.Account, error) {
|
) (*authtypes.Account, error) {
|
||||||
createdTimeMS := time.Now().UnixNano() / 1000000
|
createdTimeMS := time.Now().UnixNano() / 1000000
|
||||||
stmt := s.insertAccountStmt
|
stmt := txn.Stmt(s.insertAccountStmt)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if appserviceID == "" {
|
if appserviceID == "" {
|
||||||
|
|
@ -146,8 +146,12 @@ func (s *accountsStatements) selectAccountByLocalpart(
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *accountsStatements) selectNewNumericLocalpart(
|
func (s *accountsStatements) selectNewNumericLocalpart(
|
||||||
ctx context.Context,
|
ctx context.Context, txn *sql.Tx,
|
||||||
) (id int64, err error) {
|
) (id int64, err error) {
|
||||||
err = s.selectNewNumericLocalpartStmt.QueryRowContext(ctx).Scan(&id)
|
stmt := s.selectNewNumericLocalpartStmt
|
||||||
|
if txn != nil {
|
||||||
|
stmt = txn.Stmt(stmt)
|
||||||
|
}
|
||||||
|
err = stmt.QueryRowContext(ctx).Scan(&id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package accounts
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -12,12 +12,14 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package accounts
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
)
|
)
|
||||||
|
|
@ -51,6 +53,9 @@ const selectMembershipsByLocalpartSQL = "" +
|
||||||
const selectMembershipInRoomByLocalpartSQL = "" +
|
const selectMembershipInRoomByLocalpartSQL = "" +
|
||||||
"SELECT event_id FROM account_memberships WHERE localpart = $1 AND room_id = $2"
|
"SELECT event_id FROM account_memberships WHERE localpart = $1 AND room_id = $2"
|
||||||
|
|
||||||
|
const selectRoomIDsByLocalPartSQL = "" +
|
||||||
|
"SELECT room_id FROM account_memberships WHERE localpart = $1"
|
||||||
|
|
||||||
const deleteMembershipsByEventIDsSQL = "" +
|
const deleteMembershipsByEventIDsSQL = "" +
|
||||||
"DELETE FROM account_memberships WHERE event_id = ANY($1)"
|
"DELETE FROM account_memberships WHERE event_id = ANY($1)"
|
||||||
|
|
||||||
|
|
@ -59,6 +64,7 @@ type membershipStatements struct {
|
||||||
insertMembershipStmt *sql.Stmt
|
insertMembershipStmt *sql.Stmt
|
||||||
selectMembershipInRoomByLocalpartStmt *sql.Stmt
|
selectMembershipInRoomByLocalpartStmt *sql.Stmt
|
||||||
selectMembershipsByLocalpartStmt *sql.Stmt
|
selectMembershipsByLocalpartStmt *sql.Stmt
|
||||||
|
selectRoomIDsByLocalPartStmt *sql.Stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *membershipStatements) prepare(db *sql.DB) (err error) {
|
func (s *membershipStatements) prepare(db *sql.DB) (err error) {
|
||||||
|
|
@ -78,6 +84,9 @@ func (s *membershipStatements) prepare(db *sql.DB) (err error) {
|
||||||
if s.selectMembershipsByLocalpartStmt, err = db.Prepare(selectMembershipsByLocalpartSQL); err != nil {
|
if s.selectMembershipsByLocalpartStmt, err = db.Prepare(selectMembershipsByLocalpartSQL); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if s.selectRoomIDsByLocalPartStmt, err = db.Prepare(selectRoomIDsByLocalPartSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,15 +127,34 @@ func (s *membershipStatements) selectMembershipsByLocalpart(
|
||||||
|
|
||||||
memberships = []authtypes.Membership{}
|
memberships = []authtypes.Membership{}
|
||||||
|
|
||||||
defer rows.Close() // nolint: errcheck
|
defer common.CloseAndLogIfError(ctx, rows, "selectMembershipsByLocalpart: rows.close() failed")
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var m authtypes.Membership
|
var m authtypes.Membership
|
||||||
m.Localpart = localpart
|
m.Localpart = localpart
|
||||||
if err := rows.Scan(&m.RoomID, &m.EventID); err != nil {
|
if err = rows.Scan(&m.RoomID, &m.EventID); err != nil {
|
||||||
return nil, err
|
return
|
||||||
}
|
}
|
||||||
memberships = append(memberships, m)
|
memberships = append(memberships, m)
|
||||||
}
|
}
|
||||||
|
return memberships, rows.Err()
|
||||||
return
|
}
|
||||||
|
|
||||||
|
func (s *membershipStatements) selectRoomIDsByLocalPart(
|
||||||
|
ctx context.Context, localPart string,
|
||||||
|
) ([]string, error) {
|
||||||
|
stmt := s.selectRoomIDsByLocalPartStmt
|
||||||
|
rows, err := stmt.QueryContext(ctx, localPart)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
roomIDs := []string{}
|
||||||
|
defer rows.Close() // nolint: errcheck
|
||||||
|
for rows.Next() {
|
||||||
|
var roomID string
|
||||||
|
if err = rows.Scan(&roomID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
roomIDs = append(roomIDs, roomID)
|
||||||
|
}
|
||||||
|
return roomIDs, rows.Err()
|
||||||
}
|
}
|
||||||
107
clientapi/auth/storage/accounts/postgres/profile_table.go
Normal file
107
clientapi/auth/storage/accounts/postgres/profile_table.go
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
const profilesSchema = `
|
||||||
|
-- Stores data about accounts profiles.
|
||||||
|
CREATE TABLE IF NOT EXISTS account_profiles (
|
||||||
|
-- The Matrix user ID localpart for this account
|
||||||
|
localpart TEXT NOT NULL PRIMARY KEY,
|
||||||
|
-- The display name for this account
|
||||||
|
display_name TEXT,
|
||||||
|
-- The URL of the avatar for this account
|
||||||
|
avatar_url TEXT
|
||||||
|
);
|
||||||
|
`
|
||||||
|
|
||||||
|
const insertProfileSQL = "" +
|
||||||
|
"INSERT INTO account_profiles(localpart, display_name, avatar_url) VALUES ($1, $2, $3)"
|
||||||
|
|
||||||
|
const selectProfileByLocalpartSQL = "" +
|
||||||
|
"SELECT localpart, display_name, avatar_url FROM account_profiles WHERE localpart = $1"
|
||||||
|
|
||||||
|
const setAvatarURLSQL = "" +
|
||||||
|
"UPDATE account_profiles SET avatar_url = $1 WHERE localpart = $2"
|
||||||
|
|
||||||
|
const setDisplayNameSQL = "" +
|
||||||
|
"UPDATE account_profiles SET display_name = $1 WHERE localpart = $2"
|
||||||
|
|
||||||
|
type profilesStatements struct {
|
||||||
|
insertProfileStmt *sql.Stmt
|
||||||
|
selectProfileByLocalpartStmt *sql.Stmt
|
||||||
|
setAvatarURLStmt *sql.Stmt
|
||||||
|
setDisplayNameStmt *sql.Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *profilesStatements) prepare(db *sql.DB) (err error) {
|
||||||
|
_, err = db.Exec(profilesSchema)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.insertProfileStmt, err = db.Prepare(insertProfileSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.selectProfileByLocalpartStmt, err = db.Prepare(selectProfileByLocalpartSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.setAvatarURLStmt, err = db.Prepare(setAvatarURLSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.setDisplayNameStmt, err = db.Prepare(setDisplayNameSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *profilesStatements) insertProfile(
|
||||||
|
ctx context.Context, txn *sql.Tx, localpart string,
|
||||||
|
) (err error) {
|
||||||
|
_, err = txn.Stmt(s.insertProfileStmt).ExecContext(ctx, localpart, "", "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *profilesStatements) selectProfileByLocalpart(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) (*authtypes.Profile, error) {
|
||||||
|
var profile authtypes.Profile
|
||||||
|
err := s.selectProfileByLocalpartStmt.QueryRowContext(ctx, localpart).Scan(
|
||||||
|
&profile.Localpart, &profile.DisplayName, &profile.AvatarURL,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &profile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *profilesStatements) setAvatarURL(
|
||||||
|
ctx context.Context, localpart string, avatarURL string,
|
||||||
|
) (err error) {
|
||||||
|
_, err = s.setAvatarURLStmt.ExecContext(ctx, avatarURL, localpart)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *profilesStatements) setDisplayName(
|
||||||
|
ctx context.Context, localpart string, displayName string,
|
||||||
|
) (err error) {
|
||||||
|
_, err = s.setDisplayNameStmt.ExecContext(ctx, displayName, localpart)
|
||||||
|
return
|
||||||
|
}
|
||||||
432
clientapi/auth/storage/accounts/postgres/storage.go
Normal file
432
clientapi/auth/storage/accounts/postgres/storage.go
Normal file
|
|
@ -0,0 +1,432 @@
|
||||||
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
|
// Import the postgres database driver.
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Database represents an account database
|
||||||
|
type Database struct {
|
||||||
|
db *sql.DB
|
||||||
|
common.PartitionOffsetStatements
|
||||||
|
accounts accountsStatements
|
||||||
|
profiles profilesStatements
|
||||||
|
memberships membershipStatements
|
||||||
|
accountDatas accountDataStatements
|
||||||
|
threepids threepidStatements
|
||||||
|
filter filterStatements
|
||||||
|
serverName gomatrixserverlib.ServerName
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDatabase creates a new accounts and profiles database
|
||||||
|
func NewDatabase(dataSourceName string, serverName gomatrixserverlib.ServerName) (*Database, error) {
|
||||||
|
var db *sql.DB
|
||||||
|
var err error
|
||||||
|
if db, err = sql.Open("postgres", dataSourceName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
partitions := common.PartitionOffsetStatements{}
|
||||||
|
if err = partitions.Prepare(db, "account"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a := accountsStatements{}
|
||||||
|
if err = a.prepare(db, serverName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p := profilesStatements{}
|
||||||
|
if err = p.prepare(db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m := membershipStatements{}
|
||||||
|
if err = m.prepare(db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ac := accountDataStatements{}
|
||||||
|
if err = ac.prepare(db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t := threepidStatements{}
|
||||||
|
if err = t.prepare(db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f := filterStatements{}
|
||||||
|
if err = f.prepare(db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Database{db, partitions, a, p, m, ac, t, f, serverName}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccountByPassword returns the account associated with the given localpart and password.
|
||||||
|
// Returns sql.ErrNoRows if no account exists which matches the given localpart.
|
||||||
|
func (d *Database) GetAccountByPassword(
|
||||||
|
ctx context.Context, localpart, plaintextPassword string,
|
||||||
|
) (*authtypes.Account, error) {
|
||||||
|
hash, err := d.accounts.selectPasswordHash(ctx, localpart)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(plaintextPassword)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d.accounts.selectAccountByLocalpart(ctx, localpart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProfileByLocalpart returns the profile associated with the given localpart.
|
||||||
|
// Returns sql.ErrNoRows if no profile exists which matches the given localpart.
|
||||||
|
func (d *Database) GetProfileByLocalpart(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) (*authtypes.Profile, error) {
|
||||||
|
return d.profiles.selectProfileByLocalpart(ctx, localpart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAvatarURL updates the avatar URL of the profile associated with the given
|
||||||
|
// localpart. Returns an error if something went wrong with the SQL query
|
||||||
|
func (d *Database) SetAvatarURL(
|
||||||
|
ctx context.Context, localpart string, avatarURL string,
|
||||||
|
) error {
|
||||||
|
return d.profiles.setAvatarURL(ctx, localpart, avatarURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDisplayName updates the display name of the profile associated with the given
|
||||||
|
// localpart. Returns an error if something went wrong with the SQL query
|
||||||
|
func (d *Database) SetDisplayName(
|
||||||
|
ctx context.Context, localpart string, displayName string,
|
||||||
|
) error {
|
||||||
|
return d.profiles.setDisplayName(ctx, localpart, displayName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateGuestAccount makes a new guest account and creates an empty profile
|
||||||
|
// for this account.
|
||||||
|
func (d *Database) CreateGuestAccount(ctx context.Context) (acc *authtypes.Account, err error) {
|
||||||
|
err = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
var numLocalpart int64
|
||||||
|
numLocalpart, err = d.accounts.selectNewNumericLocalpart(ctx, txn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
localpart := strconv.FormatInt(numLocalpart, 10)
|
||||||
|
acc, err = d.createAccount(ctx, txn, localpart, "", "")
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return acc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAccount makes a new account with the given login name and password, and creates an empty profile
|
||||||
|
// for this account. If no password is supplied, the account will be a passwordless account. If the
|
||||||
|
// account already exists, it will return nil, nil.
|
||||||
|
func (d *Database) CreateAccount(
|
||||||
|
ctx context.Context, localpart, plaintextPassword, appserviceID string,
|
||||||
|
) (acc *authtypes.Account, err error) {
|
||||||
|
err = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
acc, err = d.createAccount(ctx, txn, localpart, plaintextPassword, appserviceID)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) createAccount(
|
||||||
|
ctx context.Context, txn *sql.Tx, localpart, plaintextPassword, appserviceID string,
|
||||||
|
) (*authtypes.Account, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Generate a password hash if this is not a password-less user
|
||||||
|
hash := ""
|
||||||
|
if plaintextPassword != "" {
|
||||||
|
hash, err = hashPassword(plaintextPassword)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := d.profiles.insertProfile(ctx, txn, localpart); err != nil {
|
||||||
|
if common.IsUniqueConstraintViolationErr(err) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := d.accountDatas.insertAccountData(ctx, txn, localpart, "", "m.push_rules", `{
|
||||||
|
"global": {
|
||||||
|
"content": [],
|
||||||
|
"override": [],
|
||||||
|
"room": [],
|
||||||
|
"sender": [],
|
||||||
|
"underride": []
|
||||||
|
}
|
||||||
|
}`); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d.accounts.insertAccount(ctx, txn, localpart, hash, appserviceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveMembership saves the user matching a given localpart as a member of a given
|
||||||
|
// room. It also stores the ID of the membership event.
|
||||||
|
// If a membership already exists between the user and the room, or if the
|
||||||
|
// insert fails, returns the SQL error
|
||||||
|
func (d *Database) saveMembership(
|
||||||
|
ctx context.Context, txn *sql.Tx, localpart, roomID, eventID string,
|
||||||
|
) error {
|
||||||
|
return d.memberships.insertMembership(ctx, txn, localpart, roomID, eventID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeMembershipsByEventIDs removes the memberships corresponding to the
|
||||||
|
// `join` membership events IDs in the eventIDs slice.
|
||||||
|
// If the removal fails, or if there is no membership to remove, returns an error
|
||||||
|
func (d *Database) removeMembershipsByEventIDs(
|
||||||
|
ctx context.Context, txn *sql.Tx, eventIDs []string,
|
||||||
|
) error {
|
||||||
|
return d.memberships.deleteMembershipsByEventIDs(ctx, txn, eventIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMemberships adds the "join" membership events included in a given state
|
||||||
|
// events array, and removes those which ID is included in a given array of events
|
||||||
|
// IDs. All of the process is run in a transaction, which commits only once/if every
|
||||||
|
// insertion and deletion has been successfully processed.
|
||||||
|
// Returns a SQL error if there was an issue with any part of the process
|
||||||
|
func (d *Database) UpdateMemberships(
|
||||||
|
ctx context.Context, eventsToAdd []gomatrixserverlib.Event, idsToRemove []string,
|
||||||
|
) error {
|
||||||
|
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
if err := d.removeMembershipsByEventIDs(ctx, txn, idsToRemove); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, event := range eventsToAdd {
|
||||||
|
if err := d.newMembership(ctx, txn, event); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMembershipInRoomByLocalpart returns the membership for an user
|
||||||
|
// matching the given localpart if he is a member of the room matching roomID,
|
||||||
|
// if not sql.ErrNoRows is returned.
|
||||||
|
// If there was an issue during the retrieval, returns the SQL error
|
||||||
|
func (d *Database) GetMembershipInRoomByLocalpart(
|
||||||
|
ctx context.Context, localpart, roomID string,
|
||||||
|
) (authtypes.Membership, error) {
|
||||||
|
return d.memberships.selectMembershipInRoomByLocalpart(ctx, localpart, roomID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoomIDsByLocalPart returns an array containing the room ids of all
|
||||||
|
// the rooms a user matching a given localpart is a member of
|
||||||
|
// If no membership match the given localpart, returns an empty array
|
||||||
|
// If there was an issue during the retrieval, returns the SQL error
|
||||||
|
func (d *Database) GetRoomIDsByLocalPart(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) ([]string, error) {
|
||||||
|
return d.memberships.selectRoomIDsByLocalPart(ctx, localpart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMembershipsByLocalpart returns an array containing the memberships for all
|
||||||
|
// the rooms a user matching a given localpart is a member of
|
||||||
|
// If no membership match the given localpart, returns an empty array
|
||||||
|
// If there was an issue during the retrieval, returns the SQL error
|
||||||
|
func (d *Database) GetMembershipsByLocalpart(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) (memberships []authtypes.Membership, err error) {
|
||||||
|
return d.memberships.selectMembershipsByLocalpart(ctx, localpart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newMembership saves a new membership in the database.
|
||||||
|
// If the event isn't a valid m.room.member event with type `join`, does nothing.
|
||||||
|
// If an error occurred, returns the SQL error
|
||||||
|
func (d *Database) newMembership(
|
||||||
|
ctx context.Context, txn *sql.Tx, ev gomatrixserverlib.Event,
|
||||||
|
) error {
|
||||||
|
if ev.Type() == "m.room.member" && ev.StateKey() != nil {
|
||||||
|
localpart, serverName, err := gomatrixserverlib.SplitID('@', *ev.StateKey())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only want state events from local users
|
||||||
|
if string(serverName) != string(d.serverName) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
eventID := ev.EventID()
|
||||||
|
roomID := ev.RoomID()
|
||||||
|
membership, err := ev.Membership()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only "join" membership events can be considered as new memberships
|
||||||
|
if membership == gomatrixserverlib.Join {
|
||||||
|
if err := d.saveMembership(ctx, txn, localpart, roomID, eventID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveAccountData saves new account data for a given user and a given room.
|
||||||
|
// If the account data is not specific to a room, the room ID should be an empty string
|
||||||
|
// If an account data already exists for a given set (user, room, data type), it will
|
||||||
|
// update the corresponding row with the new content
|
||||||
|
// Returns a SQL error if there was an issue with the insertion/update
|
||||||
|
func (d *Database) SaveAccountData(
|
||||||
|
ctx context.Context, localpart, roomID, dataType, content string,
|
||||||
|
) error {
|
||||||
|
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
return d.accountDatas.insertAccountData(ctx, txn, localpart, roomID, dataType, content)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccountData returns account data related to a given localpart
|
||||||
|
// If no account data could be found, returns an empty arrays
|
||||||
|
// Returns an error if there was an issue with the retrieval
|
||||||
|
func (d *Database) GetAccountData(ctx context.Context, localpart string) (
|
||||||
|
global []gomatrixserverlib.ClientEvent,
|
||||||
|
rooms map[string][]gomatrixserverlib.ClientEvent,
|
||||||
|
err error,
|
||||||
|
) {
|
||||||
|
return d.accountDatas.selectAccountData(ctx, localpart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccountDataByType returns account data matching a given
|
||||||
|
// localpart, room ID and type.
|
||||||
|
// If no account data could be found, returns nil
|
||||||
|
// Returns an error if there was an issue with the retrieval
|
||||||
|
func (d *Database) GetAccountDataByType(
|
||||||
|
ctx context.Context, localpart, roomID, dataType string,
|
||||||
|
) (data *gomatrixserverlib.ClientEvent, err error) {
|
||||||
|
return d.accountDatas.selectAccountDataByType(
|
||||||
|
ctx, localpart, roomID, dataType,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNewNumericLocalpart generates and returns a new unused numeric localpart
|
||||||
|
func (d *Database) GetNewNumericLocalpart(
|
||||||
|
ctx context.Context,
|
||||||
|
) (int64, error) {
|
||||||
|
return d.accounts.selectNewNumericLocalpart(ctx, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashPassword(plaintext string) (hash string, err error) {
|
||||||
|
hashBytes, err := bcrypt.GenerateFromPassword([]byte(plaintext), bcrypt.DefaultCost)
|
||||||
|
return string(hashBytes), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err3PIDInUse is the error returned when trying to save an association involving
|
||||||
|
// a third-party identifier which is already associated to a local user.
|
||||||
|
var Err3PIDInUse = errors.New("This third-party identifier is already in use")
|
||||||
|
|
||||||
|
// SaveThreePIDAssociation saves the association between a third party identifier
|
||||||
|
// and a local Matrix user (identified by the user's ID's local part).
|
||||||
|
// If the third-party identifier is already part of an association, returns Err3PIDInUse.
|
||||||
|
// Returns an error if there was a problem talking to the database.
|
||||||
|
func (d *Database) SaveThreePIDAssociation(
|
||||||
|
ctx context.Context, threepid, localpart, medium string,
|
||||||
|
) (err error) {
|
||||||
|
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
user, err := d.threepids.selectLocalpartForThreePID(
|
||||||
|
ctx, txn, threepid, medium,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(user) > 0 {
|
||||||
|
return Err3PIDInUse
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.threepids.insertThreePID(ctx, txn, threepid, medium, localpart)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveThreePIDAssociation removes the association involving a given third-party
|
||||||
|
// identifier.
|
||||||
|
// If no association exists involving this third-party identifier, returns nothing.
|
||||||
|
// If there was a problem talking to the database, returns an error.
|
||||||
|
func (d *Database) RemoveThreePIDAssociation(
|
||||||
|
ctx context.Context, threepid string, medium string,
|
||||||
|
) (err error) {
|
||||||
|
return d.threepids.deleteThreePID(ctx, threepid, medium)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLocalpartForThreePID looks up the localpart associated with a given third-party
|
||||||
|
// identifier.
|
||||||
|
// If no association involves the given third-party idenfitier, returns an empty
|
||||||
|
// string.
|
||||||
|
// Returns an error if there was a problem talking to the database.
|
||||||
|
func (d *Database) GetLocalpartForThreePID(
|
||||||
|
ctx context.Context, threepid string, medium string,
|
||||||
|
) (localpart string, err error) {
|
||||||
|
return d.threepids.selectLocalpartForThreePID(ctx, nil, threepid, medium)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetThreePIDsForLocalpart looks up the third-party identifiers associated with
|
||||||
|
// a given local user.
|
||||||
|
// If no association is known for this user, returns an empty slice.
|
||||||
|
// Returns an error if there was an issue talking to the database.
|
||||||
|
func (d *Database) GetThreePIDsForLocalpart(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) (threepids []authtypes.ThreePID, err error) {
|
||||||
|
return d.threepids.selectThreePIDsForLocalpart(ctx, localpart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFilter looks up the filter associated with a given local user and filter ID.
|
||||||
|
// Returns a filter structure. Otherwise returns an error if no such filter exists
|
||||||
|
// or if there was an error talking to the database.
|
||||||
|
func (d *Database) GetFilter(
|
||||||
|
ctx context.Context, localpart string, filterID string,
|
||||||
|
) (*gomatrixserverlib.Filter, error) {
|
||||||
|
return d.filter.selectFilter(ctx, localpart, filterID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutFilter puts the passed filter into the database.
|
||||||
|
// Returns the filterID as a string. Otherwise returns an error if something
|
||||||
|
// goes wrong.
|
||||||
|
func (d *Database) PutFilter(
|
||||||
|
ctx context.Context, localpart string, filter *gomatrixserverlib.Filter,
|
||||||
|
) (string, error) {
|
||||||
|
return d.filter.insertFilter(ctx, filter, localpart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckAccountAvailability checks if the username/localpart is already present
|
||||||
|
// in the database.
|
||||||
|
// If the DB returns sql.ErrNoRows the Localpart isn't taken.
|
||||||
|
func (d *Database) CheckAccountAvailability(ctx context.Context, localpart string) (bool, error) {
|
||||||
|
_, err := d.accounts.selectAccountByLocalpart(ctx, localpart)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccountByLocalpart returns the account associated with the given localpart.
|
||||||
|
// This function assumes the request is authenticated or the account data is used only internally.
|
||||||
|
// Returns sql.ErrNoRows if no account exists which matches the given localpart.
|
||||||
|
func (d *Database) GetAccountByLocalpart(ctx context.Context, localpart string,
|
||||||
|
) (*authtypes.Account, error) {
|
||||||
|
return d.accounts.selectAccountByLocalpart(ctx, localpart)
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package accounts
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package accounts
|
package sqlite3
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -39,7 +39,7 @@ CREATE TABLE IF NOT EXISTS account_data (
|
||||||
|
|
||||||
const insertAccountDataSQL = `
|
const insertAccountDataSQL = `
|
||||||
INSERT INTO account_data(localpart, room_id, type, content) VALUES($1, $2, $3, $4)
|
INSERT INTO account_data(localpart, room_id, type, content) VALUES($1, $2, $3, $4)
|
||||||
ON CONFLICT (localpart, room_id, type) DO UPDATE SET content = EXCLUDED.content
|
ON CONFLICT (localpart, room_id, type) DO UPDATE SET content = $4
|
||||||
`
|
`
|
||||||
|
|
||||||
const selectAccountDataSQL = "" +
|
const selectAccountDataSQL = "" +
|
||||||
|
|
@ -72,10 +72,9 @@ func (s *accountDataStatements) prepare(db *sql.DB) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *accountDataStatements) insertAccountData(
|
func (s *accountDataStatements) insertAccountData(
|
||||||
ctx context.Context, localpart, roomID, dataType, content string,
|
ctx context.Context, txn *sql.Tx, localpart, roomID, dataType, content string,
|
||||||
) (err error) {
|
) (err error) {
|
||||||
stmt := s.insertAccountDataStmt
|
_, err = txn.Stmt(s.insertAccountDataStmt).ExecContext(ctx, localpart, roomID, dataType, content)
|
||||||
_, err = stmt.ExecContext(ctx, localpart, roomID, dataType, content)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
155
clientapi/auth/storage/accounts/sqlite3/accounts_table.go
Normal file
155
clientapi/auth/storage/accounts/sqlite3/accounts_table.go
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/userutil"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const accountsSchema = `
|
||||||
|
-- Stores data about accounts.
|
||||||
|
CREATE TABLE IF NOT EXISTS account_accounts (
|
||||||
|
-- The Matrix user ID localpart for this account
|
||||||
|
localpart TEXT NOT NULL PRIMARY KEY,
|
||||||
|
-- When this account was first created, as a unix timestamp (ms resolution).
|
||||||
|
created_ts BIGINT NOT NULL,
|
||||||
|
-- The password hash for this account. Can be NULL if this is a passwordless account.
|
||||||
|
password_hash TEXT,
|
||||||
|
-- Identifies which application service this account belongs to, if any.
|
||||||
|
appservice_id TEXT
|
||||||
|
-- TODO:
|
||||||
|
-- is_guest, is_admin, upgraded_ts, devices, any email reset stuff?
|
||||||
|
);
|
||||||
|
`
|
||||||
|
|
||||||
|
const insertAccountSQL = "" +
|
||||||
|
"INSERT INTO account_accounts(localpart, created_ts, password_hash, appservice_id) VALUES ($1, $2, $3, $4)"
|
||||||
|
|
||||||
|
const selectAccountByLocalpartSQL = "" +
|
||||||
|
"SELECT localpart, appservice_id FROM account_accounts WHERE localpart = $1"
|
||||||
|
|
||||||
|
const selectPasswordHashSQL = "" +
|
||||||
|
"SELECT password_hash FROM account_accounts WHERE localpart = $1"
|
||||||
|
|
||||||
|
const selectNewNumericLocalpartSQL = "" +
|
||||||
|
"SELECT COUNT(localpart) FROM account_accounts"
|
||||||
|
|
||||||
|
// TODO: Update password
|
||||||
|
|
||||||
|
type accountsStatements struct {
|
||||||
|
insertAccountStmt *sql.Stmt
|
||||||
|
selectAccountByLocalpartStmt *sql.Stmt
|
||||||
|
selectPasswordHashStmt *sql.Stmt
|
||||||
|
selectNewNumericLocalpartStmt *sql.Stmt
|
||||||
|
serverName gomatrixserverlib.ServerName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *accountsStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerName) (err error) {
|
||||||
|
_, err = db.Exec(accountsSchema)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.insertAccountStmt, err = db.Prepare(insertAccountSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.selectAccountByLocalpartStmt, err = db.Prepare(selectAccountByLocalpartSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.selectPasswordHashStmt, err = db.Prepare(selectPasswordHashSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.selectNewNumericLocalpartStmt, err = db.Prepare(selectNewNumericLocalpartSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.serverName = server
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertAccount creates a new account. 'hash' should be the password hash for this account. If it is missing,
|
||||||
|
// this account will be passwordless. Returns an error if this account already exists. Returns the account
|
||||||
|
// on success.
|
||||||
|
func (s *accountsStatements) insertAccount(
|
||||||
|
ctx context.Context, txn *sql.Tx, localpart, hash, appserviceID string,
|
||||||
|
) (*authtypes.Account, error) {
|
||||||
|
createdTimeMS := time.Now().UnixNano() / 1000000
|
||||||
|
stmt := s.insertAccountStmt
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if appserviceID == "" {
|
||||||
|
_, err = txn.Stmt(stmt).ExecContext(ctx, localpart, createdTimeMS, hash, nil)
|
||||||
|
} else {
|
||||||
|
_, err = txn.Stmt(stmt).ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &authtypes.Account{
|
||||||
|
Localpart: localpart,
|
||||||
|
UserID: userutil.MakeUserID(localpart, s.serverName),
|
||||||
|
ServerName: s.serverName,
|
||||||
|
AppServiceID: appserviceID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *accountsStatements) selectPasswordHash(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) (hash string, err error) {
|
||||||
|
err = s.selectPasswordHashStmt.QueryRowContext(ctx, localpart).Scan(&hash)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *accountsStatements) selectAccountByLocalpart(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) (*authtypes.Account, error) {
|
||||||
|
var appserviceIDPtr sql.NullString
|
||||||
|
var acc authtypes.Account
|
||||||
|
|
||||||
|
stmt := s.selectAccountByLocalpartStmt
|
||||||
|
err := stmt.QueryRowContext(ctx, localpart).Scan(&acc.Localpart, &appserviceIDPtr)
|
||||||
|
if err != nil {
|
||||||
|
if err != sql.ErrNoRows {
|
||||||
|
log.WithError(err).Error("Unable to retrieve user from the db")
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if appserviceIDPtr.Valid {
|
||||||
|
acc.AppServiceID = appserviceIDPtr.String
|
||||||
|
}
|
||||||
|
|
||||||
|
acc.UserID = userutil.MakeUserID(localpart, s.serverName)
|
||||||
|
acc.ServerName = s.serverName
|
||||||
|
|
||||||
|
return &acc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *accountsStatements) selectNewNumericLocalpart(
|
||||||
|
ctx context.Context, txn *sql.Tx,
|
||||||
|
) (id int64, err error) {
|
||||||
|
stmt := s.selectNewNumericLocalpartStmt
|
||||||
|
if txn != nil {
|
||||||
|
stmt = txn.Stmt(stmt)
|
||||||
|
}
|
||||||
|
err = stmt.QueryRowContext(ctx).Scan(&id)
|
||||||
|
return
|
||||||
|
}
|
||||||
135
clientapi/auth/storage/accounts/sqlite3/filter_table.go
Normal file
135
clientapi/auth/storage/accounts/sqlite3/filter_table.go
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
// Copyright 2017 Jan Christian Grünhage
|
||||||
|
//
|
||||||
|
// 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 sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
const filterSchema = `
|
||||||
|
-- Stores data about filters
|
||||||
|
CREATE TABLE IF NOT EXISTS account_filter (
|
||||||
|
-- The filter
|
||||||
|
filter TEXT NOT NULL,
|
||||||
|
-- The ID
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
-- The localpart of the Matrix user ID associated to this filter
|
||||||
|
localpart TEXT NOT NULL,
|
||||||
|
|
||||||
|
UNIQUE (id, localpart)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS account_filter_localpart ON account_filter(localpart);
|
||||||
|
`
|
||||||
|
|
||||||
|
const selectFilterSQL = "" +
|
||||||
|
"SELECT filter FROM account_filter WHERE localpart = $1 AND id = $2"
|
||||||
|
|
||||||
|
const selectFilterIDByContentSQL = "" +
|
||||||
|
"SELECT id FROM account_filter WHERE localpart = $1 AND filter = $2"
|
||||||
|
|
||||||
|
const insertFilterSQL = "" +
|
||||||
|
"INSERT INTO account_filter (filter, localpart) VALUES ($1, $2)"
|
||||||
|
|
||||||
|
type filterStatements struct {
|
||||||
|
selectFilterStmt *sql.Stmt
|
||||||
|
selectFilterIDByContentStmt *sql.Stmt
|
||||||
|
insertFilterStmt *sql.Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *filterStatements) prepare(db *sql.DB) (err error) {
|
||||||
|
_, err = db.Exec(filterSchema)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.selectFilterStmt, err = db.Prepare(selectFilterSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.selectFilterIDByContentStmt, err = db.Prepare(selectFilterIDByContentSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.insertFilterStmt, err = db.Prepare(insertFilterSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *filterStatements) selectFilter(
|
||||||
|
ctx context.Context, localpart string, filterID string,
|
||||||
|
) (*gomatrixserverlib.Filter, error) {
|
||||||
|
// Retrieve filter from database (stored as canonical JSON)
|
||||||
|
var filterData []byte
|
||||||
|
err := s.selectFilterStmt.QueryRowContext(ctx, localpart, filterID).Scan(&filterData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal JSON into Filter struct
|
||||||
|
var filter gomatrixserverlib.Filter
|
||||||
|
if err = json.Unmarshal(filterData, &filter); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &filter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *filterStatements) insertFilter(
|
||||||
|
ctx context.Context, filter *gomatrixserverlib.Filter, localpart string,
|
||||||
|
) (filterID string, err error) {
|
||||||
|
var existingFilterID string
|
||||||
|
|
||||||
|
// Serialise json
|
||||||
|
filterJSON, err := json.Marshal(filter)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Remove whitespaces and sort JSON data
|
||||||
|
// needed to prevent from inserting the same filter multiple times
|
||||||
|
filterJSON, err = gomatrixserverlib.CanonicalJSON(filterJSON)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if filter already exists in the database using its localpart and content
|
||||||
|
//
|
||||||
|
// This can result in a race condition when two clients try to insert the
|
||||||
|
// same filter and localpart at the same time, however this is not a
|
||||||
|
// problem as both calls will result in the same filterID
|
||||||
|
err = s.selectFilterIDByContentStmt.QueryRowContext(ctx,
|
||||||
|
localpart, filterJSON).Scan(&existingFilterID)
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// If it does, return the existing ID
|
||||||
|
if existingFilterID != "" {
|
||||||
|
return existingFilterID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise insert the filter and return the new ID
|
||||||
|
res, err := s.insertFilterStmt.ExecContext(ctx, filterJSON, localpart)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
rowid, err := res.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
filterID = fmt.Sprintf("%d", rowid)
|
||||||
|
return
|
||||||
|
}
|
||||||
158
clientapi/auth/storage/accounts/sqlite3/membership_table.go
Normal file
158
clientapi/auth/storage/accounts/sqlite3/membership_table.go
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
const membershipSchema = `
|
||||||
|
-- Stores data about users memberships to rooms.
|
||||||
|
CREATE TABLE IF NOT EXISTS account_memberships (
|
||||||
|
-- The Matrix user ID localpart for the member
|
||||||
|
localpart TEXT NOT NULL,
|
||||||
|
-- The room this user is a member of
|
||||||
|
room_id TEXT NOT NULL,
|
||||||
|
-- The ID of the join membership event
|
||||||
|
event_id TEXT NOT NULL,
|
||||||
|
|
||||||
|
-- A user can only be member of a room once
|
||||||
|
PRIMARY KEY (localpart, room_id),
|
||||||
|
|
||||||
|
UNIQUE (event_id)
|
||||||
|
);
|
||||||
|
`
|
||||||
|
|
||||||
|
const insertMembershipSQL = `
|
||||||
|
INSERT INTO account_memberships(localpart, room_id, event_id) VALUES ($1, $2, $3)
|
||||||
|
ON CONFLICT (localpart, room_id) DO UPDATE SET event_id = EXCLUDED.event_id
|
||||||
|
`
|
||||||
|
|
||||||
|
const selectMembershipsByLocalpartSQL = "" +
|
||||||
|
"SELECT room_id, event_id FROM account_memberships WHERE localpart = $1"
|
||||||
|
|
||||||
|
const selectMembershipInRoomByLocalpartSQL = "" +
|
||||||
|
"SELECT event_id FROM account_memberships WHERE localpart = $1 AND room_id = $2"
|
||||||
|
|
||||||
|
const selectRoomIDsByLocalPartSQL = "" +
|
||||||
|
"SELECT room_id FROM account_memberships WHERE localpart = $1"
|
||||||
|
|
||||||
|
const deleteMembershipsByEventIDsSQL = "" +
|
||||||
|
"DELETE FROM account_memberships WHERE event_id IN ($1)"
|
||||||
|
|
||||||
|
type membershipStatements struct {
|
||||||
|
insertMembershipStmt *sql.Stmt
|
||||||
|
selectMembershipInRoomByLocalpartStmt *sql.Stmt
|
||||||
|
selectMembershipsByLocalpartStmt *sql.Stmt
|
||||||
|
selectRoomIDsByLocalPartStmt *sql.Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *membershipStatements) prepare(db *sql.DB) (err error) {
|
||||||
|
_, err = db.Exec(membershipSchema)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.insertMembershipStmt, err = db.Prepare(insertMembershipSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.selectMembershipInRoomByLocalpartStmt, err = db.Prepare(selectMembershipInRoomByLocalpartSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.selectMembershipsByLocalpartStmt, err = db.Prepare(selectMembershipsByLocalpartSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.selectRoomIDsByLocalPartStmt, err = db.Prepare(selectRoomIDsByLocalPartSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *membershipStatements) insertMembership(
|
||||||
|
ctx context.Context, txn *sql.Tx, localpart, roomID, eventID string,
|
||||||
|
) (err error) {
|
||||||
|
stmt := txn.Stmt(s.insertMembershipStmt)
|
||||||
|
_, err = stmt.ExecContext(ctx, localpart, roomID, eventID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *membershipStatements) deleteMembershipsByEventIDs(
|
||||||
|
ctx context.Context, txn *sql.Tx, eventIDs []string,
|
||||||
|
) (err error) {
|
||||||
|
sqlStr := strings.Replace(deleteMembershipsByEventIDsSQL, "($1)", common.QueryVariadic(len(eventIDs)), 1)
|
||||||
|
iEventIDs := make([]interface{}, len(eventIDs))
|
||||||
|
for i, e := range eventIDs {
|
||||||
|
iEventIDs[i] = e
|
||||||
|
}
|
||||||
|
_, err = txn.ExecContext(ctx, sqlStr, iEventIDs...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *membershipStatements) selectMembershipInRoomByLocalpart(
|
||||||
|
ctx context.Context, localpart, roomID string,
|
||||||
|
) (authtypes.Membership, error) {
|
||||||
|
membership := authtypes.Membership{Localpart: localpart, RoomID: roomID}
|
||||||
|
stmt := s.selectMembershipInRoomByLocalpartStmt
|
||||||
|
err := stmt.QueryRowContext(ctx, localpart, roomID).Scan(&membership.EventID)
|
||||||
|
|
||||||
|
return membership, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *membershipStatements) selectMembershipsByLocalpart(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) (memberships []authtypes.Membership, err error) {
|
||||||
|
stmt := s.selectMembershipsByLocalpartStmt
|
||||||
|
rows, err := stmt.QueryContext(ctx, localpart)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
memberships = []authtypes.Membership{}
|
||||||
|
|
||||||
|
defer common.CloseAndLogIfError(ctx, rows, "selectMembershipsByLocalpart: rows.close() failed")
|
||||||
|
for rows.Next() {
|
||||||
|
var m authtypes.Membership
|
||||||
|
m.Localpart = localpart
|
||||||
|
if err := rows.Scan(&m.RoomID, &m.EventID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
memberships = append(memberships, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *membershipStatements) selectRoomIDsByLocalPart(
|
||||||
|
ctx context.Context, localPart string,
|
||||||
|
) ([]string, error) {
|
||||||
|
stmt := s.selectRoomIDsByLocalPartStmt
|
||||||
|
rows, err := stmt.QueryContext(ctx, localPart)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
roomIDs := []string{}
|
||||||
|
defer rows.Close() // nolint: errcheck
|
||||||
|
for rows.Next() {
|
||||||
|
var roomID string
|
||||||
|
if err = rows.Scan(&roomID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
roomIDs = append(roomIDs, roomID)
|
||||||
|
}
|
||||||
|
return roomIDs, rows.Err()
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package accounts
|
package sqlite3
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -73,9 +73,9 @@ func (s *profilesStatements) prepare(db *sql.DB) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *profilesStatements) insertProfile(
|
func (s *profilesStatements) insertProfile(
|
||||||
ctx context.Context, localpart string,
|
ctx context.Context, txn *sql.Tx, localpart string,
|
||||||
) (err error) {
|
) (err error) {
|
||||||
_, err = s.insertProfileStmt.ExecContext(ctx, localpart, "", "")
|
_, err = txn.Stmt(s.insertProfileStmt).ExecContext(ctx, localpart, "", "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
441
clientapi/auth/storage/accounts/sqlite3/storage.go
Normal file
441
clientapi/auth/storage/accounts/sqlite3/storage.go
Normal file
|
|
@ -0,0 +1,441 @@
|
||||||
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
|
// Import the postgres database driver.
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Database represents an account database
|
||||||
|
type Database struct {
|
||||||
|
db *sql.DB
|
||||||
|
common.PartitionOffsetStatements
|
||||||
|
accounts accountsStatements
|
||||||
|
profiles profilesStatements
|
||||||
|
memberships membershipStatements
|
||||||
|
accountDatas accountDataStatements
|
||||||
|
threepids threepidStatements
|
||||||
|
filter filterStatements
|
||||||
|
serverName gomatrixserverlib.ServerName
|
||||||
|
|
||||||
|
createGuestAccountMu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDatabase creates a new accounts and profiles database
|
||||||
|
func NewDatabase(dataSourceName string, serverName gomatrixserverlib.ServerName) (*Database, error) {
|
||||||
|
var db *sql.DB
|
||||||
|
var err error
|
||||||
|
if db, err = sql.Open(common.SQLiteDriverName(), dataSourceName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
partitions := common.PartitionOffsetStatements{}
|
||||||
|
if err = partitions.Prepare(db, "account"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a := accountsStatements{}
|
||||||
|
if err = a.prepare(db, serverName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p := profilesStatements{}
|
||||||
|
if err = p.prepare(db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m := membershipStatements{}
|
||||||
|
if err = m.prepare(db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ac := accountDataStatements{}
|
||||||
|
if err = ac.prepare(db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t := threepidStatements{}
|
||||||
|
if err = t.prepare(db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f := filterStatements{}
|
||||||
|
if err = f.prepare(db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Database{db, partitions, a, p, m, ac, t, f, serverName, sync.Mutex{}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccountByPassword returns the account associated with the given localpart and password.
|
||||||
|
// Returns sql.ErrNoRows if no account exists which matches the given localpart.
|
||||||
|
func (d *Database) GetAccountByPassword(
|
||||||
|
ctx context.Context, localpart, plaintextPassword string,
|
||||||
|
) (*authtypes.Account, error) {
|
||||||
|
hash, err := d.accounts.selectPasswordHash(ctx, localpart)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(plaintextPassword)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d.accounts.selectAccountByLocalpart(ctx, localpart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProfileByLocalpart returns the profile associated with the given localpart.
|
||||||
|
// Returns sql.ErrNoRows if no profile exists which matches the given localpart.
|
||||||
|
func (d *Database) GetProfileByLocalpart(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) (*authtypes.Profile, error) {
|
||||||
|
return d.profiles.selectProfileByLocalpart(ctx, localpart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAvatarURL updates the avatar URL of the profile associated with the given
|
||||||
|
// localpart. Returns an error if something went wrong with the SQL query
|
||||||
|
func (d *Database) SetAvatarURL(
|
||||||
|
ctx context.Context, localpart string, avatarURL string,
|
||||||
|
) error {
|
||||||
|
return d.profiles.setAvatarURL(ctx, localpart, avatarURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDisplayName updates the display name of the profile associated with the given
|
||||||
|
// localpart. Returns an error if something went wrong with the SQL query
|
||||||
|
func (d *Database) SetDisplayName(
|
||||||
|
ctx context.Context, localpart string, displayName string,
|
||||||
|
) error {
|
||||||
|
return d.profiles.setDisplayName(ctx, localpart, displayName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateGuestAccount makes a new guest account and creates an empty profile
|
||||||
|
// for this account.
|
||||||
|
func (d *Database) CreateGuestAccount(ctx context.Context) (acc *authtypes.Account, err error) {
|
||||||
|
err = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
// We need to lock so we sequentially create numeric localparts. If we don't, two calls to
|
||||||
|
// this function will cause the same number to be selected and one will fail with 'database is locked'
|
||||||
|
// when the first txn upgrades to a write txn.
|
||||||
|
// We know we'll be the only process since this is sqlite ;) so a lock here will be all that is needed.
|
||||||
|
d.createGuestAccountMu.Lock()
|
||||||
|
defer d.createGuestAccountMu.Unlock()
|
||||||
|
|
||||||
|
var numLocalpart int64
|
||||||
|
numLocalpart, err = d.accounts.selectNewNumericLocalpart(ctx, txn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
localpart := strconv.FormatInt(numLocalpart, 10)
|
||||||
|
acc, err = d.createAccount(ctx, txn, localpart, "", "")
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return acc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAccount makes a new account with the given login name and password, and creates an empty profile
|
||||||
|
// for this account. If no password is supplied, the account will be a passwordless account. If the
|
||||||
|
// account already exists, it will return nil, nil.
|
||||||
|
func (d *Database) CreateAccount(
|
||||||
|
ctx context.Context, localpart, plaintextPassword, appserviceID string,
|
||||||
|
) (acc *authtypes.Account, err error) {
|
||||||
|
err = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
acc, err = d.createAccount(ctx, txn, localpart, plaintextPassword, appserviceID)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) createAccount(
|
||||||
|
ctx context.Context, txn *sql.Tx, localpart, plaintextPassword, appserviceID string,
|
||||||
|
) (*authtypes.Account, error) {
|
||||||
|
var err error
|
||||||
|
// Generate a password hash if this is not a password-less user
|
||||||
|
hash := ""
|
||||||
|
if plaintextPassword != "" {
|
||||||
|
hash, err = hashPassword(plaintextPassword)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := d.profiles.insertProfile(ctx, txn, localpart); err != nil {
|
||||||
|
if common.IsUniqueConstraintViolationErr(err) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := d.accountDatas.insertAccountData(ctx, txn, localpart, "", "m.push_rules", `{
|
||||||
|
"global": {
|
||||||
|
"content": [],
|
||||||
|
"override": [],
|
||||||
|
"room": [],
|
||||||
|
"sender": [],
|
||||||
|
"underride": []
|
||||||
|
}
|
||||||
|
}`); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d.accounts.insertAccount(ctx, txn, localpart, hash, appserviceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveMembership saves the user matching a given localpart as a member of a given
|
||||||
|
// room. It also stores the ID of the membership event.
|
||||||
|
// If a membership already exists between the user and the room, or if the
|
||||||
|
// insert fails, returns the SQL error
|
||||||
|
func (d *Database) saveMembership(
|
||||||
|
ctx context.Context, txn *sql.Tx, localpart, roomID, eventID string,
|
||||||
|
) error {
|
||||||
|
return d.memberships.insertMembership(ctx, txn, localpart, roomID, eventID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeMembershipsByEventIDs removes the memberships corresponding to the
|
||||||
|
// `join` membership events IDs in the eventIDs slice.
|
||||||
|
// If the removal fails, or if there is no membership to remove, returns an error
|
||||||
|
func (d *Database) removeMembershipsByEventIDs(
|
||||||
|
ctx context.Context, txn *sql.Tx, eventIDs []string,
|
||||||
|
) error {
|
||||||
|
return d.memberships.deleteMembershipsByEventIDs(ctx, txn, eventIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMemberships adds the "join" membership events included in a given state
|
||||||
|
// events array, and removes those which ID is included in a given array of events
|
||||||
|
// IDs. All of the process is run in a transaction, which commits only once/if every
|
||||||
|
// insertion and deletion has been successfully processed.
|
||||||
|
// Returns a SQL error if there was an issue with any part of the process
|
||||||
|
func (d *Database) UpdateMemberships(
|
||||||
|
ctx context.Context, eventsToAdd []gomatrixserverlib.Event, idsToRemove []string,
|
||||||
|
) error {
|
||||||
|
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
if err := d.removeMembershipsByEventIDs(ctx, txn, idsToRemove); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, event := range eventsToAdd {
|
||||||
|
if err := d.newMembership(ctx, txn, event); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMembershipInRoomByLocalpart returns the membership for an user
|
||||||
|
// matching the given localpart if he is a member of the room matching roomID,
|
||||||
|
// if not sql.ErrNoRows is returned.
|
||||||
|
// If there was an issue during the retrieval, returns the SQL error
|
||||||
|
func (d *Database) GetMembershipInRoomByLocalpart(
|
||||||
|
ctx context.Context, localpart, roomID string,
|
||||||
|
) (authtypes.Membership, error) {
|
||||||
|
return d.memberships.selectMembershipInRoomByLocalpart(ctx, localpart, roomID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMembershipsByLocalpart returns an array containing the memberships for all
|
||||||
|
// the rooms a user matching a given localpart is a member of
|
||||||
|
// If no membership match the given localpart, returns an empty array
|
||||||
|
// If there was an issue during the retrieval, returns the SQL error
|
||||||
|
func (d *Database) GetMembershipsByLocalpart(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) (memberships []authtypes.Membership, err error) {
|
||||||
|
return d.memberships.selectMembershipsByLocalpart(ctx, localpart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoomIDsByLocalPart returns an array containing the room ids of all
|
||||||
|
// the rooms a user matching a given localpart is a member of
|
||||||
|
// If no membership match the given localpart, returns an empty array
|
||||||
|
// If there was an issue during the retrieval, returns the SQL error
|
||||||
|
func (d *Database) GetRoomIDsByLocalPart(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) ([]string, error) {
|
||||||
|
return d.memberships.selectRoomIDsByLocalPart(ctx, localpart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newMembership saves a new membership in the database.
|
||||||
|
// If the event isn't a valid m.room.member event with type `join`, does nothing.
|
||||||
|
// If an error occurred, returns the SQL error
|
||||||
|
func (d *Database) newMembership(
|
||||||
|
ctx context.Context, txn *sql.Tx, ev gomatrixserverlib.Event,
|
||||||
|
) error {
|
||||||
|
if ev.Type() == "m.room.member" && ev.StateKey() != nil {
|
||||||
|
localpart, serverName, err := gomatrixserverlib.SplitID('@', *ev.StateKey())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only want state events from local users
|
||||||
|
if string(serverName) != string(d.serverName) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
eventID := ev.EventID()
|
||||||
|
roomID := ev.RoomID()
|
||||||
|
membership, err := ev.Membership()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only "join" membership events can be considered as new memberships
|
||||||
|
if membership == gomatrixserverlib.Join {
|
||||||
|
if err := d.saveMembership(ctx, txn, localpart, roomID, eventID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveAccountData saves new account data for a given user and a given room.
|
||||||
|
// If the account data is not specific to a room, the room ID should be an empty string
|
||||||
|
// If an account data already exists for a given set (user, room, data type), it will
|
||||||
|
// update the corresponding row with the new content
|
||||||
|
// Returns a SQL error if there was an issue with the insertion/update
|
||||||
|
func (d *Database) SaveAccountData(
|
||||||
|
ctx context.Context, localpart, roomID, dataType, content string,
|
||||||
|
) error {
|
||||||
|
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
return d.accountDatas.insertAccountData(ctx, txn, localpart, roomID, dataType, content)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccountData returns account data related to a given localpart
|
||||||
|
// If no account data could be found, returns an empty arrays
|
||||||
|
// Returns an error if there was an issue with the retrieval
|
||||||
|
func (d *Database) GetAccountData(ctx context.Context, localpart string) (
|
||||||
|
global []gomatrixserverlib.ClientEvent,
|
||||||
|
rooms map[string][]gomatrixserverlib.ClientEvent,
|
||||||
|
err error,
|
||||||
|
) {
|
||||||
|
return d.accountDatas.selectAccountData(ctx, localpart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccountDataByType returns account data matching a given
|
||||||
|
// localpart, room ID and type.
|
||||||
|
// If no account data could be found, returns nil
|
||||||
|
// Returns an error if there was an issue with the retrieval
|
||||||
|
func (d *Database) GetAccountDataByType(
|
||||||
|
ctx context.Context, localpart, roomID, dataType string,
|
||||||
|
) (data *gomatrixserverlib.ClientEvent, err error) {
|
||||||
|
return d.accountDatas.selectAccountDataByType(
|
||||||
|
ctx, localpart, roomID, dataType,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNewNumericLocalpart generates and returns a new unused numeric localpart
|
||||||
|
func (d *Database) GetNewNumericLocalpart(
|
||||||
|
ctx context.Context,
|
||||||
|
) (int64, error) {
|
||||||
|
return d.accounts.selectNewNumericLocalpart(ctx, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashPassword(plaintext string) (hash string, err error) {
|
||||||
|
hashBytes, err := bcrypt.GenerateFromPassword([]byte(plaintext), bcrypt.DefaultCost)
|
||||||
|
return string(hashBytes), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err3PIDInUse is the error returned when trying to save an association involving
|
||||||
|
// a third-party identifier which is already associated to a local user.
|
||||||
|
var Err3PIDInUse = errors.New("This third-party identifier is already in use")
|
||||||
|
|
||||||
|
// SaveThreePIDAssociation saves the association between a third party identifier
|
||||||
|
// and a local Matrix user (identified by the user's ID's local part).
|
||||||
|
// If the third-party identifier is already part of an association, returns Err3PIDInUse.
|
||||||
|
// Returns an error if there was a problem talking to the database.
|
||||||
|
func (d *Database) SaveThreePIDAssociation(
|
||||||
|
ctx context.Context, threepid, localpart, medium string,
|
||||||
|
) (err error) {
|
||||||
|
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
user, err := d.threepids.selectLocalpartForThreePID(
|
||||||
|
ctx, txn, threepid, medium,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(user) > 0 {
|
||||||
|
return Err3PIDInUse
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.threepids.insertThreePID(ctx, txn, threepid, medium, localpart)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveThreePIDAssociation removes the association involving a given third-party
|
||||||
|
// identifier.
|
||||||
|
// If no association exists involving this third-party identifier, returns nothing.
|
||||||
|
// If there was a problem talking to the database, returns an error.
|
||||||
|
func (d *Database) RemoveThreePIDAssociation(
|
||||||
|
ctx context.Context, threepid string, medium string,
|
||||||
|
) (err error) {
|
||||||
|
return d.threepids.deleteThreePID(ctx, threepid, medium)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLocalpartForThreePID looks up the localpart associated with a given third-party
|
||||||
|
// identifier.
|
||||||
|
// If no association involves the given third-party idenfitier, returns an empty
|
||||||
|
// string.
|
||||||
|
// Returns an error if there was a problem talking to the database.
|
||||||
|
func (d *Database) GetLocalpartForThreePID(
|
||||||
|
ctx context.Context, threepid string, medium string,
|
||||||
|
) (localpart string, err error) {
|
||||||
|
return d.threepids.selectLocalpartForThreePID(ctx, nil, threepid, medium)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetThreePIDsForLocalpart looks up the third-party identifiers associated with
|
||||||
|
// a given local user.
|
||||||
|
// If no association is known for this user, returns an empty slice.
|
||||||
|
// Returns an error if there was an issue talking to the database.
|
||||||
|
func (d *Database) GetThreePIDsForLocalpart(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) (threepids []authtypes.ThreePID, err error) {
|
||||||
|
return d.threepids.selectThreePIDsForLocalpart(ctx, localpart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFilter looks up the filter associated with a given local user and filter ID.
|
||||||
|
// Returns a filter structure. Otherwise returns an error if no such filter exists
|
||||||
|
// or if there was an error talking to the database.
|
||||||
|
func (d *Database) GetFilter(
|
||||||
|
ctx context.Context, localpart string, filterID string,
|
||||||
|
) (*gomatrixserverlib.Filter, error) {
|
||||||
|
return d.filter.selectFilter(ctx, localpart, filterID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutFilter puts the passed filter into the database.
|
||||||
|
// Returns the filterID as a string. Otherwise returns an error if something
|
||||||
|
// goes wrong.
|
||||||
|
func (d *Database) PutFilter(
|
||||||
|
ctx context.Context, localpart string, filter *gomatrixserverlib.Filter,
|
||||||
|
) (string, error) {
|
||||||
|
return d.filter.insertFilter(ctx, filter, localpart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckAccountAvailability checks if the username/localpart is already present
|
||||||
|
// in the database.
|
||||||
|
// If the DB returns sql.ErrNoRows the Localpart isn't taken.
|
||||||
|
func (d *Database) CheckAccountAvailability(ctx context.Context, localpart string) (bool, error) {
|
||||||
|
_, err := d.accounts.selectAccountByLocalpart(ctx, localpart)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccountByLocalpart returns the account associated with the given localpart.
|
||||||
|
// This function assumes the request is authenticated or the account data is used only internally.
|
||||||
|
// Returns sql.ErrNoRows if no account exists which matches the given localpart.
|
||||||
|
func (d *Database) GetAccountByLocalpart(ctx context.Context, localpart string,
|
||||||
|
) (*authtypes.Account, error) {
|
||||||
|
return d.accounts.selectAccountByLocalpart(ctx, localpart)
|
||||||
|
}
|
||||||
129
clientapi/auth/storage/accounts/sqlite3/threepid_table.go
Normal file
129
clientapi/auth/storage/accounts/sqlite3/threepid_table.go
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
const threepidSchema = `
|
||||||
|
-- Stores data about third party identifiers
|
||||||
|
CREATE TABLE IF NOT EXISTS account_threepid (
|
||||||
|
-- The third party identifier
|
||||||
|
threepid TEXT NOT NULL,
|
||||||
|
-- The 3PID medium
|
||||||
|
medium TEXT NOT NULL DEFAULT 'email',
|
||||||
|
-- The localpart of the Matrix user ID associated to this 3PID
|
||||||
|
localpart TEXT NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY(threepid, medium)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS account_threepid_localpart ON account_threepid(localpart);
|
||||||
|
`
|
||||||
|
|
||||||
|
const selectLocalpartForThreePIDSQL = "" +
|
||||||
|
"SELECT localpart FROM account_threepid WHERE threepid = $1 AND medium = $2"
|
||||||
|
|
||||||
|
const selectThreePIDsForLocalpartSQL = "" +
|
||||||
|
"SELECT threepid, medium FROM account_threepid WHERE localpart = $1"
|
||||||
|
|
||||||
|
const insertThreePIDSQL = "" +
|
||||||
|
"INSERT INTO account_threepid (threepid, medium, localpart) VALUES ($1, $2, $3)"
|
||||||
|
|
||||||
|
const deleteThreePIDSQL = "" +
|
||||||
|
"DELETE FROM account_threepid WHERE threepid = $1 AND medium = $2"
|
||||||
|
|
||||||
|
type threepidStatements struct {
|
||||||
|
selectLocalpartForThreePIDStmt *sql.Stmt
|
||||||
|
selectThreePIDsForLocalpartStmt *sql.Stmt
|
||||||
|
insertThreePIDStmt *sql.Stmt
|
||||||
|
deleteThreePIDStmt *sql.Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *threepidStatements) prepare(db *sql.DB) (err error) {
|
||||||
|
_, err = db.Exec(threepidSchema)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.selectLocalpartForThreePIDStmt, err = db.Prepare(selectLocalpartForThreePIDSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.selectThreePIDsForLocalpartStmt, err = db.Prepare(selectThreePIDsForLocalpartSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.insertThreePIDStmt, err = db.Prepare(insertThreePIDSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.deleteThreePIDStmt, err = db.Prepare(deleteThreePIDSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *threepidStatements) selectLocalpartForThreePID(
|
||||||
|
ctx context.Context, txn *sql.Tx, threepid string, medium string,
|
||||||
|
) (localpart string, err error) {
|
||||||
|
stmt := common.TxStmt(txn, s.selectLocalpartForThreePIDStmt)
|
||||||
|
err = stmt.QueryRowContext(ctx, threepid, medium).Scan(&localpart)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *threepidStatements) selectThreePIDsForLocalpart(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) (threepids []authtypes.ThreePID, err error) {
|
||||||
|
rows, err := s.selectThreePIDsForLocalpartStmt.QueryContext(ctx, localpart)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer common.CloseAndLogIfError(ctx, rows, "selectThreePIDsForLocalpart: rows.close() failed")
|
||||||
|
|
||||||
|
threepids = []authtypes.ThreePID{}
|
||||||
|
for rows.Next() {
|
||||||
|
var threepid string
|
||||||
|
var medium string
|
||||||
|
if err = rows.Scan(&threepid, &medium); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
threepids = append(threepids, authtypes.ThreePID{
|
||||||
|
Address: threepid,
|
||||||
|
Medium: medium,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return threepids, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *threepidStatements) insertThreePID(
|
||||||
|
ctx context.Context, txn *sql.Tx, threepid, medium, localpart string,
|
||||||
|
) (err error) {
|
||||||
|
stmt := common.TxStmt(txn, s.insertThreePIDStmt)
|
||||||
|
_, err = stmt.ExecContext(ctx, threepid, medium, localpart)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *threepidStatements) deleteThreePID(
|
||||||
|
ctx context.Context, threepid string, medium string) (err error) {
|
||||||
|
_, err = s.deleteThreePIDStmt.ExecContext(ctx, threepid, medium)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,381 +12,29 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build !wasm
|
||||||
|
|
||||||
package accounts
|
package accounts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"net/url"
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/postgres"
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/sqlite3"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
|
|
||||||
// Import the postgres database driver.
|
|
||||||
_ "github.com/lib/pq"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Database represents an account database
|
func NewDatabase(dataSourceName string, serverName gomatrixserverlib.ServerName) (Database, error) {
|
||||||
type Database struct {
|
uri, err := url.Parse(dataSourceName)
|
||||||
db *sql.DB
|
|
||||||
common.PartitionOffsetStatements
|
|
||||||
accounts accountsStatements
|
|
||||||
profiles profilesStatements
|
|
||||||
memberships membershipStatements
|
|
||||||
accountDatas accountDataStatements
|
|
||||||
threepids threepidStatements
|
|
||||||
filter filterStatements
|
|
||||||
serverName gomatrixserverlib.ServerName
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDatabase creates a new accounts and profiles database
|
|
||||||
func NewDatabase(dataSourceName string, serverName gomatrixserverlib.ServerName) (*Database, error) {
|
|
||||||
var db *sql.DB
|
|
||||||
var err error
|
|
||||||
if db, err = sql.Open("postgres", dataSourceName); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
partitions := common.PartitionOffsetStatements{}
|
|
||||||
if err = partitions.Prepare(db, "account"); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
a := accountsStatements{}
|
|
||||||
if err = a.prepare(db, serverName); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p := profilesStatements{}
|
|
||||||
if err = p.prepare(db); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m := membershipStatements{}
|
|
||||||
if err = m.prepare(db); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ac := accountDataStatements{}
|
|
||||||
if err = ac.prepare(db); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
t := threepidStatements{}
|
|
||||||
if err = t.prepare(db); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
f := filterStatements{}
|
|
||||||
if err = f.prepare(db); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Database{db, partitions, a, p, m, ac, t, f, serverName}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAccountByPassword returns the account associated with the given localpart and password.
|
|
||||||
// Returns sql.ErrNoRows if no account exists which matches the given localpart.
|
|
||||||
func (d *Database) GetAccountByPassword(
|
|
||||||
ctx context.Context, localpart, plaintextPassword string,
|
|
||||||
) (*authtypes.Account, error) {
|
|
||||||
hash, err := d.accounts.selectPasswordHash(ctx, localpart)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return postgres.NewDatabase(dataSourceName, serverName)
|
||||||
}
|
}
|
||||||
if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(plaintextPassword)); err != nil {
|
switch uri.Scheme {
|
||||||
return nil, err
|
case "postgres":
|
||||||
}
|
return postgres.NewDatabase(dataSourceName, serverName)
|
||||||
return d.accounts.selectAccountByLocalpart(ctx, localpart)
|
case "file":
|
||||||
}
|
return sqlite3.NewDatabase(dataSourceName, serverName)
|
||||||
|
default:
|
||||||
// GetProfileByLocalpart returns the profile associated with the given localpart.
|
return postgres.NewDatabase(dataSourceName, serverName)
|
||||||
// Returns sql.ErrNoRows if no profile exists which matches the given localpart.
|
|
||||||
func (d *Database) GetProfileByLocalpart(
|
|
||||||
ctx context.Context, localpart string,
|
|
||||||
) (*authtypes.Profile, error) {
|
|
||||||
return d.profiles.selectProfileByLocalpart(ctx, localpart)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAvatarURL updates the avatar URL of the profile associated with the given
|
|
||||||
// localpart. Returns an error if something went wrong with the SQL query
|
|
||||||
func (d *Database) SetAvatarURL(
|
|
||||||
ctx context.Context, localpart string, avatarURL string,
|
|
||||||
) error {
|
|
||||||
return d.profiles.setAvatarURL(ctx, localpart, avatarURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDisplayName updates the display name of the profile associated with the given
|
|
||||||
// localpart. Returns an error if something went wrong with the SQL query
|
|
||||||
func (d *Database) SetDisplayName(
|
|
||||||
ctx context.Context, localpart string, displayName string,
|
|
||||||
) error {
|
|
||||||
return d.profiles.setDisplayName(ctx, localpart, displayName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateAccount makes a new account with the given login name and password, and creates an empty profile
|
|
||||||
// for this account. If no password is supplied, the account will be a passwordless account. If the
|
|
||||||
// account already exists, it will return nil, nil.
|
|
||||||
func (d *Database) CreateAccount(
|
|
||||||
ctx context.Context, localpart, plaintextPassword, appserviceID string,
|
|
||||||
) (*authtypes.Account, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Generate a password hash if this is not a password-less user
|
|
||||||
hash := ""
|
|
||||||
if plaintextPassword != "" {
|
|
||||||
hash, err = hashPassword(plaintextPassword)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := d.profiles.insertProfile(ctx, localpart); err != nil {
|
|
||||||
if common.IsUniqueConstraintViolationErr(err) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := d.SaveAccountData(ctx, localpart, "", "m.push_rules", `{
|
|
||||||
"global": {
|
|
||||||
"content": [],
|
|
||||||
"override": [],
|
|
||||||
"room": [],
|
|
||||||
"sender": [],
|
|
||||||
"underride": []
|
|
||||||
}
|
|
||||||
}`); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return d.accounts.insertAccount(ctx, localpart, hash, appserviceID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveMembership saves the user matching a given localpart as a member of a given
|
|
||||||
// room. It also stores the ID of the membership event.
|
|
||||||
// If a membership already exists between the user and the room, or if the
|
|
||||||
// insert fails, returns the SQL error
|
|
||||||
func (d *Database) saveMembership(
|
|
||||||
ctx context.Context, txn *sql.Tx, localpart, roomID, eventID string,
|
|
||||||
) error {
|
|
||||||
return d.memberships.insertMembership(ctx, txn, localpart, roomID, eventID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeMembershipsByEventIDs removes the memberships corresponding to the
|
|
||||||
// `join` membership events IDs in the eventIDs slice.
|
|
||||||
// If the removal fails, or if there is no membership to remove, returns an error
|
|
||||||
func (d *Database) removeMembershipsByEventIDs(
|
|
||||||
ctx context.Context, txn *sql.Tx, eventIDs []string,
|
|
||||||
) error {
|
|
||||||
return d.memberships.deleteMembershipsByEventIDs(ctx, txn, eventIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateMemberships adds the "join" membership events included in a given state
|
|
||||||
// events array, and removes those which ID is included in a given array of events
|
|
||||||
// IDs. All of the process is run in a transaction, which commits only once/if every
|
|
||||||
// insertion and deletion has been successfully processed.
|
|
||||||
// Returns a SQL error if there was an issue with any part of the process
|
|
||||||
func (d *Database) UpdateMemberships(
|
|
||||||
ctx context.Context, eventsToAdd []gomatrixserverlib.Event, idsToRemove []string,
|
|
||||||
) error {
|
|
||||||
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
|
||||||
if err := d.removeMembershipsByEventIDs(ctx, txn, idsToRemove); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, event := range eventsToAdd {
|
|
||||||
if err := d.newMembership(ctx, txn, event); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMembershipInRoomByLocalpart returns the membership for an user
|
|
||||||
// matching the given localpart if he is a member of the room matching roomID,
|
|
||||||
// if not sql.ErrNoRows is returned.
|
|
||||||
// If there was an issue during the retrieval, returns the SQL error
|
|
||||||
func (d *Database) GetMembershipInRoomByLocalpart(
|
|
||||||
ctx context.Context, localpart, roomID string,
|
|
||||||
) (authtypes.Membership, error) {
|
|
||||||
return d.memberships.selectMembershipInRoomByLocalpart(ctx, localpart, roomID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMembershipsByLocalpart returns an array containing the memberships for all
|
|
||||||
// the rooms a user matching a given localpart is a member of
|
|
||||||
// If no membership match the given localpart, returns an empty array
|
|
||||||
// If there was an issue during the retrieval, returns the SQL error
|
|
||||||
func (d *Database) GetMembershipsByLocalpart(
|
|
||||||
ctx context.Context, localpart string,
|
|
||||||
) (memberships []authtypes.Membership, err error) {
|
|
||||||
return d.memberships.selectMembershipsByLocalpart(ctx, localpart)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newMembership saves a new membership in the database.
|
|
||||||
// If the event isn't a valid m.room.member event with type `join`, does nothing.
|
|
||||||
// If an error occurred, returns the SQL error
|
|
||||||
func (d *Database) newMembership(
|
|
||||||
ctx context.Context, txn *sql.Tx, ev gomatrixserverlib.Event,
|
|
||||||
) error {
|
|
||||||
if ev.Type() == "m.room.member" && ev.StateKey() != nil {
|
|
||||||
localpart, serverName, err := gomatrixserverlib.SplitID('@', *ev.StateKey())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We only want state events from local users
|
|
||||||
if string(serverName) != string(d.serverName) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
eventID := ev.EventID()
|
|
||||||
roomID := ev.RoomID()
|
|
||||||
membership, err := ev.Membership()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only "join" membership events can be considered as new memberships
|
|
||||||
if membership == gomatrixserverlib.Join {
|
|
||||||
if err := d.saveMembership(ctx, txn, localpart, roomID, eventID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveAccountData saves new account data for a given user and a given room.
|
|
||||||
// If the account data is not specific to a room, the room ID should be an empty string
|
|
||||||
// If an account data already exists for a given set (user, room, data type), it will
|
|
||||||
// update the corresponding row with the new content
|
|
||||||
// Returns a SQL error if there was an issue with the insertion/update
|
|
||||||
func (d *Database) SaveAccountData(
|
|
||||||
ctx context.Context, localpart, roomID, dataType, content string,
|
|
||||||
) error {
|
|
||||||
return d.accountDatas.insertAccountData(ctx, localpart, roomID, dataType, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAccountData returns account data related to a given localpart
|
|
||||||
// If no account data could be found, returns an empty arrays
|
|
||||||
// Returns an error if there was an issue with the retrieval
|
|
||||||
func (d *Database) GetAccountData(ctx context.Context, localpart string) (
|
|
||||||
global []gomatrixserverlib.ClientEvent,
|
|
||||||
rooms map[string][]gomatrixserverlib.ClientEvent,
|
|
||||||
err error,
|
|
||||||
) {
|
|
||||||
return d.accountDatas.selectAccountData(ctx, localpart)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAccountDataByType returns account data matching a given
|
|
||||||
// localpart, room ID and type.
|
|
||||||
// If no account data could be found, returns nil
|
|
||||||
// Returns an error if there was an issue with the retrieval
|
|
||||||
func (d *Database) GetAccountDataByType(
|
|
||||||
ctx context.Context, localpart, roomID, dataType string,
|
|
||||||
) (data *gomatrixserverlib.ClientEvent, err error) {
|
|
||||||
return d.accountDatas.selectAccountDataByType(
|
|
||||||
ctx, localpart, roomID, dataType,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetNewNumericLocalpart generates and returns a new unused numeric localpart
|
|
||||||
func (d *Database) GetNewNumericLocalpart(
|
|
||||||
ctx context.Context,
|
|
||||||
) (int64, error) {
|
|
||||||
return d.accounts.selectNewNumericLocalpart(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashPassword(plaintext string) (hash string, err error) {
|
|
||||||
hashBytes, err := bcrypt.GenerateFromPassword([]byte(plaintext), bcrypt.DefaultCost)
|
|
||||||
return string(hashBytes), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Err3PIDInUse is the error returned when trying to save an association involving
|
|
||||||
// a third-party identifier which is already associated to a local user.
|
|
||||||
var Err3PIDInUse = errors.New("This third-party identifier is already in use")
|
|
||||||
|
|
||||||
// SaveThreePIDAssociation saves the association between a third party identifier
|
|
||||||
// and a local Matrix user (identified by the user's ID's local part).
|
|
||||||
// If the third-party identifier is already part of an association, returns Err3PIDInUse.
|
|
||||||
// Returns an error if there was a problem talking to the database.
|
|
||||||
func (d *Database) SaveThreePIDAssociation(
|
|
||||||
ctx context.Context, threepid, localpart, medium string,
|
|
||||||
) (err error) {
|
|
||||||
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
|
||||||
user, err := d.threepids.selectLocalpartForThreePID(
|
|
||||||
ctx, txn, threepid, medium,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(user) > 0 {
|
|
||||||
return Err3PIDInUse
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.threepids.insertThreePID(ctx, txn, threepid, medium, localpart)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveThreePIDAssociation removes the association involving a given third-party
|
|
||||||
// identifier.
|
|
||||||
// If no association exists involving this third-party identifier, returns nothing.
|
|
||||||
// If there was a problem talking to the database, returns an error.
|
|
||||||
func (d *Database) RemoveThreePIDAssociation(
|
|
||||||
ctx context.Context, threepid string, medium string,
|
|
||||||
) (err error) {
|
|
||||||
return d.threepids.deleteThreePID(ctx, threepid, medium)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLocalpartForThreePID looks up the localpart associated with a given third-party
|
|
||||||
// identifier.
|
|
||||||
// If no association involves the given third-party idenfitier, returns an empty
|
|
||||||
// string.
|
|
||||||
// Returns an error if there was a problem talking to the database.
|
|
||||||
func (d *Database) GetLocalpartForThreePID(
|
|
||||||
ctx context.Context, threepid string, medium string,
|
|
||||||
) (localpart string, err error) {
|
|
||||||
return d.threepids.selectLocalpartForThreePID(ctx, nil, threepid, medium)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetThreePIDsForLocalpart looks up the third-party identifiers associated with
|
|
||||||
// a given local user.
|
|
||||||
// If no association is known for this user, returns an empty slice.
|
|
||||||
// Returns an error if there was an issue talking to the database.
|
|
||||||
func (d *Database) GetThreePIDsForLocalpart(
|
|
||||||
ctx context.Context, localpart string,
|
|
||||||
) (threepids []authtypes.ThreePID, err error) {
|
|
||||||
return d.threepids.selectThreePIDsForLocalpart(ctx, localpart)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFilter looks up the filter associated with a given local user and filter ID.
|
|
||||||
// Returns a filter structure. Otherwise returns an error if no such filter exists
|
|
||||||
// or if there was an error talking to the database.
|
|
||||||
func (d *Database) GetFilter(
|
|
||||||
ctx context.Context, localpart string, filterID string,
|
|
||||||
) (*gomatrixserverlib.Filter, error) {
|
|
||||||
return d.filter.selectFilter(ctx, localpart, filterID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutFilter puts the passed filter into the database.
|
|
||||||
// Returns the filterID as a string. Otherwise returns an error if something
|
|
||||||
// goes wrong.
|
|
||||||
func (d *Database) PutFilter(
|
|
||||||
ctx context.Context, localpart string, filter *gomatrixserverlib.Filter,
|
|
||||||
) (string, error) {
|
|
||||||
return d.filter.insertFilter(ctx, filter, localpart)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckAccountAvailability checks if the username/localpart is already present
|
|
||||||
// in the database.
|
|
||||||
// If the DB returns sql.ErrNoRows the Localpart isn't taken.
|
|
||||||
func (d *Database) CheckAccountAvailability(ctx context.Context, localpart string) (bool, error) {
|
|
||||||
_, err := d.accounts.selectAccountByLocalpart(ctx, localpart)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAccountByLocalpart returns the account associated with the given localpart.
|
|
||||||
// This function assumes the request is authenticated or the account data is used only internally.
|
|
||||||
// Returns sql.ErrNoRows if no account exists which matches the given localpart.
|
|
||||||
func (d *Database) GetAccountByLocalpart(ctx context.Context, localpart string,
|
|
||||||
) (*authtypes.Account, error) {
|
|
||||||
return d.accounts.selectAccountByLocalpart(ctx, localpart)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
38
clientapi/auth/storage/accounts/storage_wasm.go
Normal file
38
clientapi/auth/storage/accounts/storage_wasm.go
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package accounts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/sqlite3"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewDatabase(dataSourceName string, serverName gomatrixserverlib.ServerName) (Database, error) {
|
||||||
|
uri, err := url.Parse(dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Cannot use postgres implementation")
|
||||||
|
}
|
||||||
|
switch uri.Scheme {
|
||||||
|
case "postgres":
|
||||||
|
return nil, fmt.Errorf("Cannot use postgres implementation")
|
||||||
|
case "file":
|
||||||
|
return sqlite3.NewDatabase(dataSourceName, serverName)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Cannot use postgres implementation")
|
||||||
|
}
|
||||||
|
}
|
||||||
32
clientapi/auth/storage/devices/interface.go
Normal file
32
clientapi/auth/storage/devices/interface.go
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package devices
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Database interface {
|
||||||
|
GetDeviceByAccessToken(ctx context.Context, token string) (*authtypes.Device, error)
|
||||||
|
GetDeviceByID(ctx context.Context, localpart, deviceID string) (*authtypes.Device, error)
|
||||||
|
GetDevicesByLocalpart(ctx context.Context, localpart string) ([]authtypes.Device, error)
|
||||||
|
CreateDevice(ctx context.Context, localpart string, deviceID *string, accessToken string, displayName *string) (dev *authtypes.Device, returnErr error)
|
||||||
|
UpdateDevice(ctx context.Context, localpart, deviceID string, displayName *string) error
|
||||||
|
RemoveDevice(ctx context.Context, deviceID, localpart string) error
|
||||||
|
RemoveDevices(ctx context.Context, localpart string, devices []string) error
|
||||||
|
RemoveAllDevices(ctx context.Context, localpart string) error
|
||||||
|
}
|
||||||
|
|
@ -12,17 +12,17 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package devices
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/lib/pq"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/userutil"
|
"github.com/matrix-org/dendrite/clientapi/userutil"
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -80,6 +80,9 @@ const deleteDeviceSQL = "" +
|
||||||
const deleteDevicesByLocalpartSQL = "" +
|
const deleteDevicesByLocalpartSQL = "" +
|
||||||
"DELETE FROM device_devices WHERE localpart = $1"
|
"DELETE FROM device_devices WHERE localpart = $1"
|
||||||
|
|
||||||
|
const deleteDevicesSQL = "" +
|
||||||
|
"DELETE FROM device_devices WHERE localpart = $1 AND device_id = ANY($2)"
|
||||||
|
|
||||||
type devicesStatements struct {
|
type devicesStatements struct {
|
||||||
insertDeviceStmt *sql.Stmt
|
insertDeviceStmt *sql.Stmt
|
||||||
selectDeviceByTokenStmt *sql.Stmt
|
selectDeviceByTokenStmt *sql.Stmt
|
||||||
|
|
@ -88,6 +91,7 @@ type devicesStatements struct {
|
||||||
updateDeviceNameStmt *sql.Stmt
|
updateDeviceNameStmt *sql.Stmt
|
||||||
deleteDeviceStmt *sql.Stmt
|
deleteDeviceStmt *sql.Stmt
|
||||||
deleteDevicesByLocalpartStmt *sql.Stmt
|
deleteDevicesByLocalpartStmt *sql.Stmt
|
||||||
|
deleteDevicesStmt *sql.Stmt
|
||||||
serverName gomatrixserverlib.ServerName
|
serverName gomatrixserverlib.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,6 +121,9 @@ func (s *devicesStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerN
|
||||||
if s.deleteDevicesByLocalpartStmt, err = db.Prepare(deleteDevicesByLocalpartSQL); err != nil {
|
if s.deleteDevicesByLocalpartStmt, err = db.Prepare(deleteDevicesByLocalpartSQL); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if s.deleteDevicesStmt, err = db.Prepare(deleteDevicesSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
s.serverName = server
|
s.serverName = server
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -142,6 +149,7 @@ func (s *devicesStatements) insertDevice(
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deleteDevice removes a single device by id and user localpart.
|
||||||
func (s *devicesStatements) deleteDevice(
|
func (s *devicesStatements) deleteDevice(
|
||||||
ctx context.Context, txn *sql.Tx, id, localpart string,
|
ctx context.Context, txn *sql.Tx, id, localpart string,
|
||||||
) error {
|
) error {
|
||||||
|
|
@ -150,6 +158,18 @@ func (s *devicesStatements) deleteDevice(
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deleteDevices removes a single or multiple devices by ids and user localpart.
|
||||||
|
// Returns an error if the execution failed.
|
||||||
|
func (s *devicesStatements) deleteDevices(
|
||||||
|
ctx context.Context, txn *sql.Tx, localpart string, devices []string,
|
||||||
|
) error {
|
||||||
|
stmt := common.TxStmt(txn, s.deleteDevicesStmt)
|
||||||
|
_, err := stmt.ExecContext(ctx, localpart, pq.Array(devices))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteDevicesByLocalpart removes all devices for the
|
||||||
|
// given user localpart.
|
||||||
func (s *devicesStatements) deleteDevicesByLocalpart(
|
func (s *devicesStatements) deleteDevicesByLocalpart(
|
||||||
ctx context.Context, txn *sql.Tx, localpart string,
|
ctx context.Context, txn *sql.Tx, localpart string,
|
||||||
) error {
|
) error {
|
||||||
|
|
@ -206,10 +226,11 @@ func (s *devicesStatements) selectDevicesByLocalpart(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return devices, err
|
return devices, err
|
||||||
}
|
}
|
||||||
|
defer common.CloseAndLogIfError(ctx, rows, "selectDevicesByLocalpart: rows.close() failed")
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var dev authtypes.Device
|
var dev authtypes.Device
|
||||||
err = rows.Scan(&dev.ID)
|
err = rows.Scan(&dev.ID, &dev.DisplayName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return devices, err
|
return devices, err
|
||||||
}
|
}
|
||||||
|
|
@ -217,5 +238,5 @@ func (s *devicesStatements) selectDevicesByLocalpart(
|
||||||
devices = append(devices, dev)
|
devices = append(devices, dev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return devices, nil
|
return devices, rows.Err()
|
||||||
}
|
}
|
||||||
182
clientapi/auth/storage/devices/postgres/storage.go
Normal file
182
clientapi/auth/storage/devices/postgres/storage.go
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/base64"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The length of generated device IDs
|
||||||
|
var deviceIDByteLength = 6
|
||||||
|
|
||||||
|
// Database represents a device database.
|
||||||
|
type Database struct {
|
||||||
|
db *sql.DB
|
||||||
|
devices devicesStatements
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDatabase creates a new device database
|
||||||
|
func NewDatabase(dataSourceName string, serverName gomatrixserverlib.ServerName) (*Database, error) {
|
||||||
|
var db *sql.DB
|
||||||
|
var err error
|
||||||
|
if db, err = sql.Open("postgres", dataSourceName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
d := devicesStatements{}
|
||||||
|
if err = d.prepare(db, serverName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Database{db, d}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDeviceByAccessToken returns the device matching the given access token.
|
||||||
|
// Returns sql.ErrNoRows if no matching device was found.
|
||||||
|
func (d *Database) GetDeviceByAccessToken(
|
||||||
|
ctx context.Context, token string,
|
||||||
|
) (*authtypes.Device, error) {
|
||||||
|
return d.devices.selectDeviceByToken(ctx, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDeviceByID returns the device matching the given ID.
|
||||||
|
// Returns sql.ErrNoRows if no matching device was found.
|
||||||
|
func (d *Database) GetDeviceByID(
|
||||||
|
ctx context.Context, localpart, deviceID string,
|
||||||
|
) (*authtypes.Device, error) {
|
||||||
|
return d.devices.selectDeviceByID(ctx, localpart, deviceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDevicesByLocalpart returns the devices matching the given localpart.
|
||||||
|
func (d *Database) GetDevicesByLocalpart(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) ([]authtypes.Device, error) {
|
||||||
|
return d.devices.selectDevicesByLocalpart(ctx, localpart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDevice makes a new device associated with the given user ID localpart.
|
||||||
|
// If there is already a device with the same device ID for this user, that access token will be revoked
|
||||||
|
// and replaced with the given accessToken. If the given accessToken is already in use for another device,
|
||||||
|
// an error will be returned.
|
||||||
|
// If no device ID is given one is generated.
|
||||||
|
// Returns the device on success.
|
||||||
|
func (d *Database) CreateDevice(
|
||||||
|
ctx context.Context, localpart string, deviceID *string, accessToken string,
|
||||||
|
displayName *string,
|
||||||
|
) (dev *authtypes.Device, returnErr error) {
|
||||||
|
if deviceID != nil {
|
||||||
|
returnErr = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
var err error
|
||||||
|
// Revoke existing tokens for this device
|
||||||
|
if err = d.devices.deleteDevice(ctx, txn, *deviceID, localpart); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dev, err = d.devices.insertDevice(ctx, txn, *deviceID, localpart, accessToken, displayName)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// We generate device IDs in a loop in case its already taken.
|
||||||
|
// We cap this at going round 5 times to ensure we don't spin forever
|
||||||
|
var newDeviceID string
|
||||||
|
for i := 1; i <= 5; i++ {
|
||||||
|
newDeviceID, returnErr = generateDeviceID()
|
||||||
|
if returnErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
returnErr = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
var err error
|
||||||
|
dev, err = d.devices.insertDevice(ctx, txn, newDeviceID, localpart, accessToken, displayName)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if returnErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateDeviceID creates a new device id. Returns an error if failed to generate
|
||||||
|
// random bytes.
|
||||||
|
func generateDeviceID() (string, error) {
|
||||||
|
b := make([]byte, deviceIDByteLength)
|
||||||
|
_, err := rand.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// url-safe no padding
|
||||||
|
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDevice updates the given device with the display name.
|
||||||
|
// Returns SQL error if there are problems and nil on success.
|
||||||
|
func (d *Database) UpdateDevice(
|
||||||
|
ctx context.Context, localpart, deviceID string, displayName *string,
|
||||||
|
) error {
|
||||||
|
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
return d.devices.updateDeviceName(ctx, txn, localpart, deviceID, displayName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDevice revokes a device by deleting the entry in the database
|
||||||
|
// matching with the given device ID and user ID localpart.
|
||||||
|
// If the device doesn't exist, it will not return an error
|
||||||
|
// If something went wrong during the deletion, it will return the SQL error.
|
||||||
|
func (d *Database) RemoveDevice(
|
||||||
|
ctx context.Context, deviceID, localpart string,
|
||||||
|
) error {
|
||||||
|
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
if err := d.devices.deleteDevice(ctx, txn, deviceID, localpart); err != sql.ErrNoRows {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDevices revokes one or more devices by deleting the entry in the database
|
||||||
|
// matching with the given device IDs and user ID localpart.
|
||||||
|
// If the devices don't exist, it will not return an error
|
||||||
|
// If something went wrong during the deletion, it will return the SQL error.
|
||||||
|
func (d *Database) RemoveDevices(
|
||||||
|
ctx context.Context, localpart string, devices []string,
|
||||||
|
) error {
|
||||||
|
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
if err := d.devices.deleteDevices(ctx, txn, localpart, devices); err != sql.ErrNoRows {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAllDevices revokes devices by deleting the entry in the
|
||||||
|
// database matching the given user ID localpart.
|
||||||
|
// If something went wrong during the deletion, it will return the SQL error.
|
||||||
|
func (d *Database) RemoveAllDevices(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) error {
|
||||||
|
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
if err := d.devices.deleteDevicesByLocalpart(ctx, txn, localpart); err != sql.ErrNoRows {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
243
clientapi/auth/storage/devices/sqlite3/devices_table.go
Normal file
243
clientapi/auth/storage/devices/sqlite3/devices_table.go
Normal file
|
|
@ -0,0 +1,243 @@
|
||||||
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/userutil"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
const devicesSchema = `
|
||||||
|
-- This sequence is used for automatic allocation of session_id.
|
||||||
|
-- CREATE SEQUENCE IF NOT EXISTS device_session_id_seq START 1;
|
||||||
|
|
||||||
|
-- Stores data about devices.
|
||||||
|
CREATE TABLE IF NOT EXISTS device_devices (
|
||||||
|
access_token TEXT PRIMARY KEY,
|
||||||
|
session_id INTEGER,
|
||||||
|
device_id TEXT ,
|
||||||
|
localpart TEXT ,
|
||||||
|
created_ts BIGINT,
|
||||||
|
display_name TEXT,
|
||||||
|
|
||||||
|
UNIQUE (localpart, device_id)
|
||||||
|
);
|
||||||
|
`
|
||||||
|
|
||||||
|
const insertDeviceSQL = "" +
|
||||||
|
"INSERT INTO device_devices (device_id, localpart, access_token, created_ts, display_name, session_id)" +
|
||||||
|
" VALUES ($1, $2, $3, $4, $5, $6)"
|
||||||
|
|
||||||
|
const selectDevicesCountSQL = "" +
|
||||||
|
"SELECT COUNT(access_token) FROM device_devices"
|
||||||
|
|
||||||
|
const selectDeviceByTokenSQL = "" +
|
||||||
|
"SELECT session_id, device_id, localpart FROM device_devices WHERE access_token = $1"
|
||||||
|
|
||||||
|
const selectDeviceByIDSQL = "" +
|
||||||
|
"SELECT display_name FROM device_devices WHERE localpart = $1 and device_id = $2"
|
||||||
|
|
||||||
|
const selectDevicesByLocalpartSQL = "" +
|
||||||
|
"SELECT device_id, display_name FROM device_devices WHERE localpart = $1"
|
||||||
|
|
||||||
|
const updateDeviceNameSQL = "" +
|
||||||
|
"UPDATE device_devices SET display_name = $1 WHERE localpart = $2 AND device_id = $3"
|
||||||
|
|
||||||
|
const deleteDeviceSQL = "" +
|
||||||
|
"DELETE FROM device_devices WHERE device_id = $1 AND localpart = $2"
|
||||||
|
|
||||||
|
const deleteDevicesByLocalpartSQL = "" +
|
||||||
|
"DELETE FROM device_devices WHERE localpart = $1"
|
||||||
|
|
||||||
|
const deleteDevicesSQL = "" +
|
||||||
|
"DELETE FROM device_devices WHERE localpart = $1 AND device_id IN ($2)"
|
||||||
|
|
||||||
|
type devicesStatements struct {
|
||||||
|
db *sql.DB
|
||||||
|
insertDeviceStmt *sql.Stmt
|
||||||
|
selectDevicesCountStmt *sql.Stmt
|
||||||
|
selectDeviceByTokenStmt *sql.Stmt
|
||||||
|
selectDeviceByIDStmt *sql.Stmt
|
||||||
|
selectDevicesByLocalpartStmt *sql.Stmt
|
||||||
|
updateDeviceNameStmt *sql.Stmt
|
||||||
|
deleteDeviceStmt *sql.Stmt
|
||||||
|
deleteDevicesByLocalpartStmt *sql.Stmt
|
||||||
|
serverName gomatrixserverlib.ServerName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *devicesStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerName) (err error) {
|
||||||
|
s.db = db
|
||||||
|
_, err = db.Exec(devicesSchema)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.insertDeviceStmt, err = db.Prepare(insertDeviceSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.selectDevicesCountStmt, err = db.Prepare(selectDevicesCountSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.selectDeviceByTokenStmt, err = db.Prepare(selectDeviceByTokenSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.selectDeviceByIDStmt, err = db.Prepare(selectDeviceByIDSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.selectDevicesByLocalpartStmt, err = db.Prepare(selectDevicesByLocalpartSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.updateDeviceNameStmt, err = db.Prepare(updateDeviceNameSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.deleteDeviceStmt, err = db.Prepare(deleteDeviceSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.deleteDevicesByLocalpartStmt, err = db.Prepare(deleteDevicesByLocalpartSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.serverName = server
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertDevice creates a new device. Returns an error if any device with the same access token already exists.
|
||||||
|
// Returns an error if the user already has a device with the given device ID.
|
||||||
|
// Returns the device on success.
|
||||||
|
func (s *devicesStatements) insertDevice(
|
||||||
|
ctx context.Context, txn *sql.Tx, id, localpart, accessToken string,
|
||||||
|
displayName *string,
|
||||||
|
) (*authtypes.Device, error) {
|
||||||
|
createdTimeMS := time.Now().UnixNano() / 1000000
|
||||||
|
var sessionID int64
|
||||||
|
countStmt := common.TxStmt(txn, s.selectDevicesCountStmt)
|
||||||
|
insertStmt := common.TxStmt(txn, s.insertDeviceStmt)
|
||||||
|
if err := countStmt.QueryRowContext(ctx).Scan(&sessionID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sessionID++
|
||||||
|
if _, err := insertStmt.ExecContext(ctx, id, localpart, accessToken, createdTimeMS, displayName, sessionID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &authtypes.Device{
|
||||||
|
ID: id,
|
||||||
|
UserID: userutil.MakeUserID(localpart, s.serverName),
|
||||||
|
AccessToken: accessToken,
|
||||||
|
SessionID: sessionID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *devicesStatements) deleteDevice(
|
||||||
|
ctx context.Context, txn *sql.Tx, id, localpart string,
|
||||||
|
) error {
|
||||||
|
stmt := common.TxStmt(txn, s.deleteDeviceStmt)
|
||||||
|
_, err := stmt.ExecContext(ctx, id, localpart)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *devicesStatements) deleteDevices(
|
||||||
|
ctx context.Context, txn *sql.Tx, localpart string, devices []string,
|
||||||
|
) error {
|
||||||
|
orig := strings.Replace(deleteDevicesSQL, "($1)", common.QueryVariadic(len(devices)), 1)
|
||||||
|
prep, err := s.db.Prepare(orig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stmt := common.TxStmt(txn, prep)
|
||||||
|
params := make([]interface{}, len(devices)+1)
|
||||||
|
params[0] = localpart
|
||||||
|
for i, v := range devices {
|
||||||
|
params[i+1] = v
|
||||||
|
}
|
||||||
|
params = append(params, params...)
|
||||||
|
_, err = stmt.ExecContext(ctx, params...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *devicesStatements) deleteDevicesByLocalpart(
|
||||||
|
ctx context.Context, txn *sql.Tx, localpart string,
|
||||||
|
) error {
|
||||||
|
stmt := common.TxStmt(txn, s.deleteDevicesByLocalpartStmt)
|
||||||
|
_, err := stmt.ExecContext(ctx, localpart)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *devicesStatements) updateDeviceName(
|
||||||
|
ctx context.Context, txn *sql.Tx, localpart, deviceID string, displayName *string,
|
||||||
|
) error {
|
||||||
|
stmt := common.TxStmt(txn, s.updateDeviceNameStmt)
|
||||||
|
_, err := stmt.ExecContext(ctx, displayName, localpart, deviceID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *devicesStatements) selectDeviceByToken(
|
||||||
|
ctx context.Context, accessToken string,
|
||||||
|
) (*authtypes.Device, error) {
|
||||||
|
var dev authtypes.Device
|
||||||
|
var localpart string
|
||||||
|
stmt := s.selectDeviceByTokenStmt
|
||||||
|
err := stmt.QueryRowContext(ctx, accessToken).Scan(&dev.SessionID, &dev.ID, &localpart)
|
||||||
|
if err == nil {
|
||||||
|
dev.UserID = userutil.MakeUserID(localpart, s.serverName)
|
||||||
|
dev.AccessToken = accessToken
|
||||||
|
}
|
||||||
|
return &dev, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// selectDeviceByID retrieves a device from the database with the given user
|
||||||
|
// localpart and deviceID
|
||||||
|
func (s *devicesStatements) selectDeviceByID(
|
||||||
|
ctx context.Context, localpart, deviceID string,
|
||||||
|
) (*authtypes.Device, error) {
|
||||||
|
var dev authtypes.Device
|
||||||
|
var created sql.NullInt64
|
||||||
|
stmt := s.selectDeviceByIDStmt
|
||||||
|
err := stmt.QueryRowContext(ctx, localpart, deviceID).Scan(&created)
|
||||||
|
if err == nil {
|
||||||
|
dev.ID = deviceID
|
||||||
|
dev.UserID = userutil.MakeUserID(localpart, s.serverName)
|
||||||
|
}
|
||||||
|
return &dev, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *devicesStatements) selectDevicesByLocalpart(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) ([]authtypes.Device, error) {
|
||||||
|
devices := []authtypes.Device{}
|
||||||
|
|
||||||
|
rows, err := s.selectDevicesByLocalpartStmt.QueryContext(ctx, localpart)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return devices, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var dev authtypes.Device
|
||||||
|
err = rows.Scan(&dev.ID, &dev.DisplayName)
|
||||||
|
if err != nil {
|
||||||
|
return devices, err
|
||||||
|
}
|
||||||
|
dev.UserID = userutil.MakeUserID(localpart, s.serverName)
|
||||||
|
devices = append(devices, dev)
|
||||||
|
}
|
||||||
|
|
||||||
|
return devices, nil
|
||||||
|
}
|
||||||
184
clientapi/auth/storage/devices/sqlite3/storage.go
Normal file
184
clientapi/auth/storage/devices/sqlite3/storage.go
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/base64"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The length of generated device IDs
|
||||||
|
var deviceIDByteLength = 6
|
||||||
|
|
||||||
|
// Database represents a device database.
|
||||||
|
type Database struct {
|
||||||
|
db *sql.DB
|
||||||
|
devices devicesStatements
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDatabase creates a new device database
|
||||||
|
func NewDatabase(dataSourceName string, serverName gomatrixserverlib.ServerName) (*Database, error) {
|
||||||
|
var db *sql.DB
|
||||||
|
var err error
|
||||||
|
if db, err = sql.Open(common.SQLiteDriverName(), dataSourceName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
d := devicesStatements{}
|
||||||
|
if err = d.prepare(db, serverName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Database{db, d}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDeviceByAccessToken returns the device matching the given access token.
|
||||||
|
// Returns sql.ErrNoRows if no matching device was found.
|
||||||
|
func (d *Database) GetDeviceByAccessToken(
|
||||||
|
ctx context.Context, token string,
|
||||||
|
) (*authtypes.Device, error) {
|
||||||
|
return d.devices.selectDeviceByToken(ctx, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDeviceByID returns the device matching the given ID.
|
||||||
|
// Returns sql.ErrNoRows if no matching device was found.
|
||||||
|
func (d *Database) GetDeviceByID(
|
||||||
|
ctx context.Context, localpart, deviceID string,
|
||||||
|
) (*authtypes.Device, error) {
|
||||||
|
return d.devices.selectDeviceByID(ctx, localpart, deviceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDevicesByLocalpart returns the devices matching the given localpart.
|
||||||
|
func (d *Database) GetDevicesByLocalpart(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) ([]authtypes.Device, error) {
|
||||||
|
return d.devices.selectDevicesByLocalpart(ctx, localpart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDevice makes a new device associated with the given user ID localpart.
|
||||||
|
// If there is already a device with the same device ID for this user, that access token will be revoked
|
||||||
|
// and replaced with the given accessToken. If the given accessToken is already in use for another device,
|
||||||
|
// an error will be returned.
|
||||||
|
// If no device ID is given one is generated.
|
||||||
|
// Returns the device on success.
|
||||||
|
func (d *Database) CreateDevice(
|
||||||
|
ctx context.Context, localpart string, deviceID *string, accessToken string,
|
||||||
|
displayName *string,
|
||||||
|
) (dev *authtypes.Device, returnErr error) {
|
||||||
|
if deviceID != nil {
|
||||||
|
returnErr = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
var err error
|
||||||
|
// Revoke existing tokens for this device
|
||||||
|
if err = d.devices.deleteDevice(ctx, txn, *deviceID, localpart); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dev, err = d.devices.insertDevice(ctx, txn, *deviceID, localpart, accessToken, displayName)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// We generate device IDs in a loop in case its already taken.
|
||||||
|
// We cap this at going round 5 times to ensure we don't spin forever
|
||||||
|
var newDeviceID string
|
||||||
|
for i := 1; i <= 5; i++ {
|
||||||
|
newDeviceID, returnErr = generateDeviceID()
|
||||||
|
if returnErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
returnErr = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
var err error
|
||||||
|
dev, err = d.devices.insertDevice(ctx, txn, newDeviceID, localpart, accessToken, displayName)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if returnErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateDeviceID creates a new device id. Returns an error if failed to generate
|
||||||
|
// random bytes.
|
||||||
|
func generateDeviceID() (string, error) {
|
||||||
|
b := make([]byte, deviceIDByteLength)
|
||||||
|
_, err := rand.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// url-safe no padding
|
||||||
|
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDevice updates the given device with the display name.
|
||||||
|
// Returns SQL error if there are problems and nil on success.
|
||||||
|
func (d *Database) UpdateDevice(
|
||||||
|
ctx context.Context, localpart, deviceID string, displayName *string,
|
||||||
|
) error {
|
||||||
|
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
return d.devices.updateDeviceName(ctx, txn, localpart, deviceID, displayName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDevice revokes a device by deleting the entry in the database
|
||||||
|
// matching with the given device ID and user ID localpart.
|
||||||
|
// If the device doesn't exist, it will not return an error
|
||||||
|
// If something went wrong during the deletion, it will return the SQL error.
|
||||||
|
func (d *Database) RemoveDevice(
|
||||||
|
ctx context.Context, deviceID, localpart string,
|
||||||
|
) error {
|
||||||
|
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
if err := d.devices.deleteDevice(ctx, txn, deviceID, localpart); err != sql.ErrNoRows {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDevices revokes one or more devices by deleting the entry in the database
|
||||||
|
// matching with the given device IDs and user ID localpart.
|
||||||
|
// If the devices don't exist, it will not return an error
|
||||||
|
// If something went wrong during the deletion, it will return the SQL error.
|
||||||
|
func (d *Database) RemoveDevices(
|
||||||
|
ctx context.Context, localpart string, devices []string,
|
||||||
|
) error {
|
||||||
|
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
if err := d.devices.deleteDevices(ctx, txn, localpart, devices); err != sql.ErrNoRows {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAllDevices revokes devices by deleting the entry in the
|
||||||
|
// database matching the given user ID localpart.
|
||||||
|
// If something went wrong during the deletion, it will return the SQL error.
|
||||||
|
func (d *Database) RemoveAllDevices(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) error {
|
||||||
|
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
if err := d.devices.deleteDevicesByLocalpart(ctx, txn, localpart); err != sql.ErrNoRows {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,156 +12,29 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build !wasm
|
||||||
|
|
||||||
package devices
|
package devices
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"net/url"
|
||||||
"crypto/rand"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/base64"
|
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices/postgres"
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices/sqlite3"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The length of generated device IDs
|
func NewDatabase(dataSourceName string, serverName gomatrixserverlib.ServerName) (Database, error) {
|
||||||
var deviceIDByteLength = 6
|
uri, err := url.Parse(dataSourceName)
|
||||||
|
|
||||||
// Database represents a device database.
|
|
||||||
type Database struct {
|
|
||||||
db *sql.DB
|
|
||||||
devices devicesStatements
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDatabase creates a new device database
|
|
||||||
func NewDatabase(dataSourceName string, serverName gomatrixserverlib.ServerName) (*Database, error) {
|
|
||||||
var db *sql.DB
|
|
||||||
var err error
|
|
||||||
if db, err = sql.Open("postgres", dataSourceName); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
d := devicesStatements{}
|
|
||||||
if err = d.prepare(db, serverName); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Database{db, d}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDeviceByAccessToken returns the device matching the given access token.
|
|
||||||
// Returns sql.ErrNoRows if no matching device was found.
|
|
||||||
func (d *Database) GetDeviceByAccessToken(
|
|
||||||
ctx context.Context, token string,
|
|
||||||
) (*authtypes.Device, error) {
|
|
||||||
return d.devices.selectDeviceByToken(ctx, token)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDeviceByID returns the device matching the given ID.
|
|
||||||
// Returns sql.ErrNoRows if no matching device was found.
|
|
||||||
func (d *Database) GetDeviceByID(
|
|
||||||
ctx context.Context, localpart, deviceID string,
|
|
||||||
) (*authtypes.Device, error) {
|
|
||||||
return d.devices.selectDeviceByID(ctx, localpart, deviceID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDevicesByLocalpart returns the devices matching the given localpart.
|
|
||||||
func (d *Database) GetDevicesByLocalpart(
|
|
||||||
ctx context.Context, localpart string,
|
|
||||||
) ([]authtypes.Device, error) {
|
|
||||||
return d.devices.selectDevicesByLocalpart(ctx, localpart)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateDevice makes a new device associated with the given user ID localpart.
|
|
||||||
// If there is already a device with the same device ID for this user, that access token will be revoked
|
|
||||||
// and replaced with the given accessToken. If the given accessToken is already in use for another device,
|
|
||||||
// an error will be returned.
|
|
||||||
// If no device ID is given one is generated.
|
|
||||||
// Returns the device on success.
|
|
||||||
func (d *Database) CreateDevice(
|
|
||||||
ctx context.Context, localpart string, deviceID *string, accessToken string,
|
|
||||||
displayName *string,
|
|
||||||
) (dev *authtypes.Device, returnErr error) {
|
|
||||||
if deviceID != nil {
|
|
||||||
returnErr = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
|
||||||
var err error
|
|
||||||
// Revoke existing tokens for this device
|
|
||||||
if err = d.devices.deleteDevice(ctx, txn, *deviceID, localpart); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dev, err = d.devices.insertDevice(ctx, txn, *deviceID, localpart, accessToken, displayName)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// We generate device IDs in a loop in case its already taken.
|
|
||||||
// We cap this at going round 5 times to ensure we don't spin forever
|
|
||||||
var newDeviceID string
|
|
||||||
for i := 1; i <= 5; i++ {
|
|
||||||
newDeviceID, returnErr = generateDeviceID()
|
|
||||||
if returnErr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
returnErr = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
|
||||||
var err error
|
|
||||||
dev, err = d.devices.insertDevice(ctx, txn, newDeviceID, localpart, accessToken, displayName)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if returnErr == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateDeviceID creates a new device id. Returns an error if failed to generate
|
|
||||||
// random bytes.
|
|
||||||
func generateDeviceID() (string, error) {
|
|
||||||
b := make([]byte, deviceIDByteLength)
|
|
||||||
_, err := rand.Read(b)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return postgres.NewDatabase(dataSourceName, serverName)
|
||||||
}
|
}
|
||||||
// url-safe no padding
|
switch uri.Scheme {
|
||||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
case "postgres":
|
||||||
|
return postgres.NewDatabase(dataSourceName, serverName)
|
||||||
|
case "file":
|
||||||
|
return sqlite3.NewDatabase(dataSourceName, serverName)
|
||||||
|
default:
|
||||||
|
return postgres.NewDatabase(dataSourceName, serverName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateDevice updates the given device with the display name.
|
|
||||||
// Returns SQL error if there are problems and nil on success.
|
|
||||||
func (d *Database) UpdateDevice(
|
|
||||||
ctx context.Context, localpart, deviceID string, displayName *string,
|
|
||||||
) error {
|
|
||||||
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
|
||||||
return d.devices.updateDeviceName(ctx, txn, localpart, deviceID, displayName)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveDevice revokes a device by deleting the entry in the database
|
|
||||||
// matching with the given device ID and user ID localpart.
|
|
||||||
// If the device doesn't exist, it will not return an error
|
|
||||||
// If something went wrong during the deletion, it will return the SQL error.
|
|
||||||
func (d *Database) RemoveDevice(
|
|
||||||
ctx context.Context, deviceID, localpart string,
|
|
||||||
) error {
|
|
||||||
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
|
||||||
if err := d.devices.deleteDevice(ctx, txn, deviceID, localpart); err != sql.ErrNoRows {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveAllDevices revokes devices by deleting the entry in the
|
|
||||||
// database matching the given user ID localpart.
|
|
||||||
// If something went wrong during the deletion, it will return the SQL error.
|
|
||||||
func (d *Database) RemoveAllDevices(
|
|
||||||
ctx context.Context, localpart string,
|
|
||||||
) error {
|
|
||||||
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
|
||||||
if err := d.devices.deleteDevicesByLocalpart(ctx, txn, localpart); err != sql.ErrNoRows {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
38
clientapi/auth/storage/devices/storage_wasm.go
Normal file
38
clientapi/auth/storage/devices/storage_wasm.go
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package devices
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices/sqlite3"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewDatabase(dataSourceName string, serverName gomatrixserverlib.ServerName) (Database, error) {
|
||||||
|
uri, err := url.Parse(dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Cannot use postgres implementation")
|
||||||
|
}
|
||||||
|
switch uri.Scheme {
|
||||||
|
case "postgres":
|
||||||
|
return nil, fmt.Errorf("Cannot use postgres implementation")
|
||||||
|
case "file":
|
||||||
|
return sqlite3.NewDatabase(dataSourceName, serverName)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Cannot use postgres implementation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,9 +23,9 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/routing"
|
"github.com/matrix-org/dendrite/clientapi/routing"
|
||||||
"github.com/matrix-org/dendrite/common/basecomponent"
|
"github.com/matrix-org/dendrite/common/basecomponent"
|
||||||
"github.com/matrix-org/dendrite/common/transactions"
|
"github.com/matrix-org/dendrite/common/transactions"
|
||||||
|
eduServerAPI "github.com/matrix-org/dendrite/eduserver/api"
|
||||||
federationSenderAPI "github.com/matrix-org/dendrite/federationsender/api"
|
federationSenderAPI "github.com/matrix-org/dendrite/federationsender/api"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
typingServerAPI "github.com/matrix-org/dendrite/typingserver/api"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
@ -34,20 +34,20 @@ import (
|
||||||
// component.
|
// component.
|
||||||
func SetupClientAPIComponent(
|
func SetupClientAPIComponent(
|
||||||
base *basecomponent.BaseDendrite,
|
base *basecomponent.BaseDendrite,
|
||||||
deviceDB *devices.Database,
|
deviceDB devices.Database,
|
||||||
accountsDB *accounts.Database,
|
accountsDB accounts.Database,
|
||||||
federation *gomatrixserverlib.FederationClient,
|
federation *gomatrixserverlib.FederationClient,
|
||||||
keyRing *gomatrixserverlib.KeyRing,
|
keyRing *gomatrixserverlib.KeyRing,
|
||||||
aliasAPI roomserverAPI.RoomserverAliasAPI,
|
aliasAPI roomserverAPI.RoomserverAliasAPI,
|
||||||
inputAPI roomserverAPI.RoomserverInputAPI,
|
inputAPI roomserverAPI.RoomserverInputAPI,
|
||||||
queryAPI roomserverAPI.RoomserverQueryAPI,
|
queryAPI roomserverAPI.RoomserverQueryAPI,
|
||||||
typingInputAPI typingServerAPI.TypingServerInputAPI,
|
eduInputAPI eduServerAPI.EDUServerInputAPI,
|
||||||
asAPI appserviceAPI.AppServiceQueryAPI,
|
asAPI appserviceAPI.AppServiceQueryAPI,
|
||||||
transactionsCache *transactions.Cache,
|
transactionsCache *transactions.Cache,
|
||||||
fedSenderAPI federationSenderAPI.FederationSenderQueryAPI,
|
fedSenderAPI federationSenderAPI.FederationSenderQueryAPI,
|
||||||
) {
|
) {
|
||||||
roomserverProducer := producers.NewRoomserverProducer(inputAPI)
|
roomserverProducer := producers.NewRoomserverProducer(inputAPI, queryAPI)
|
||||||
typingProducer := producers.NewTypingServerProducer(typingInputAPI)
|
eduProducer := producers.NewEDUServerProducer(eduInputAPI)
|
||||||
|
|
||||||
userUpdateProducer := &producers.UserUpdateProducer{
|
userUpdateProducer := &producers.UserUpdateProducer{
|
||||||
Producer: base.KafkaProducer,
|
Producer: base.KafkaProducer,
|
||||||
|
|
@ -67,8 +67,8 @@ func SetupClientAPIComponent(
|
||||||
}
|
}
|
||||||
|
|
||||||
routing.Setup(
|
routing.Setup(
|
||||||
base.APIMux, *base.Cfg, roomserverProducer, queryAPI, aliasAPI, asAPI,
|
base.APIMux, base.Cfg, roomserverProducer, queryAPI, aliasAPI, asAPI,
|
||||||
accountsDB, deviceDB, federation, *keyRing, userUpdateProducer,
|
accountsDB, deviceDB, federation, *keyRing, userUpdateProducer,
|
||||||
syncProducer, typingProducer, transactionsCache, fedSenderAPI,
|
syncProducer, eduProducer, transactionsCache, fedSenderAPI,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ import (
|
||||||
// OutputRoomEventConsumer consumes events that originated in the room server.
|
// OutputRoomEventConsumer consumes events that originated in the room server.
|
||||||
type OutputRoomEventConsumer struct {
|
type OutputRoomEventConsumer struct {
|
||||||
roomServerConsumer *common.ContinualConsumer
|
roomServerConsumer *common.ContinualConsumer
|
||||||
db *accounts.Database
|
db accounts.Database
|
||||||
query api.RoomserverQueryAPI
|
query api.RoomserverQueryAPI
|
||||||
serverName string
|
serverName string
|
||||||
}
|
}
|
||||||
|
|
@ -40,7 +40,7 @@ type OutputRoomEventConsumer struct {
|
||||||
func NewOutputRoomEventConsumer(
|
func NewOutputRoomEventConsumer(
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
kafkaConsumer sarama.Consumer,
|
kafkaConsumer sarama.Consumer,
|
||||||
store *accounts.Database,
|
store accounts.Database,
|
||||||
queryAPI api.RoomserverQueryAPI,
|
queryAPI api.RoomserverQueryAPI,
|
||||||
) *OutputRoomEventConsumer {
|
) *OutputRoomEventConsumer {
|
||||||
|
|
||||||
|
|
@ -91,7 +91,7 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error {
|
||||||
"type": ev.Type(),
|
"type": ev.Type(),
|
||||||
}).Info("received event from roomserver")
|
}).Info("received event from roomserver")
|
||||||
|
|
||||||
events, err := s.lookupStateEvents(output.NewRoomEvent.AddsStateEventIDs, ev)
|
events, err := s.lookupStateEvents(output.NewRoomEvent.AddsStateEventIDs, ev.Event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +138,9 @@ func (s *OutputRoomEventConsumer) lookupStateEvents(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
result = append(result, eventResp.Events...)
|
for _, headeredEvent := range eventResp.Events {
|
||||||
|
result = append(result, headeredEvent.Event)
|
||||||
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,11 +36,3 @@ func UnmarshalJSONRequest(req *http.Request, iface interface{}) *util.JSONRespon
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogThenError logs the given error then returns a matrix-compliant 500 internal server error response.
|
|
||||||
// This should be used to log fatal errors which require investigation. It should not be used
|
|
||||||
// to log client validation errors, etc.
|
|
||||||
func LogThenError(req *http.Request, err error) util.JSONResponse {
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("request failed")
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,12 @@ func GuestAccessForbidden(msg string) *MatrixError {
|
||||||
return &MatrixError{"M_GUEST_ACCESS_FORBIDDEN", msg}
|
return &MatrixError{"M_GUEST_ACCESS_FORBIDDEN", msg}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnsupportedRoomVersion is an error which is returned when the client
|
||||||
|
// requests a room with a version that is unsupported.
|
||||||
|
func UnsupportedRoomVersion(msg string) *MatrixError {
|
||||||
|
return &MatrixError{"M_UNSUPPORTED_ROOM_VERSION", msg}
|
||||||
|
}
|
||||||
|
|
||||||
// LimitExceededError is a rate-limiting error.
|
// LimitExceededError is a rate-limiting error.
|
||||||
type LimitExceededError struct {
|
type LimitExceededError struct {
|
||||||
MatrixError
|
MatrixError
|
||||||
|
|
|
||||||
|
|
@ -16,32 +16,32 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/typingserver/api"
|
"github.com/matrix-org/dendrite/eduserver/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TypingServerProducer produces events for the typing server to consume
|
// EDUServerProducer produces events for the EDU server to consume
|
||||||
type TypingServerProducer struct {
|
type EDUServerProducer struct {
|
||||||
InputAPI api.TypingServerInputAPI
|
InputAPI api.EDUServerInputAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTypingServerProducer creates a new TypingServerProducer
|
// NewEDUServerProducer creates a new EDUServerProducer
|
||||||
func NewTypingServerProducer(inputAPI api.TypingServerInputAPI) *TypingServerProducer {
|
func NewEDUServerProducer(inputAPI api.EDUServerInputAPI) *EDUServerProducer {
|
||||||
return &TypingServerProducer{
|
return &EDUServerProducer{
|
||||||
InputAPI: inputAPI,
|
InputAPI: inputAPI,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send typing event to typing server
|
// SendTyping sends a typing event to EDU server
|
||||||
func (p *TypingServerProducer) Send(
|
func (p *EDUServerProducer) SendTyping(
|
||||||
ctx context.Context, userID, roomID string,
|
ctx context.Context, userID, roomID string,
|
||||||
typing bool, timeout int64,
|
typing bool, timeoutMS int64,
|
||||||
) error {
|
) error {
|
||||||
requestData := api.InputTypingEvent{
|
requestData := api.InputTypingEvent{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
Typing: typing,
|
Typing: typing,
|
||||||
Timeout: timeout,
|
TimeoutMS: timeoutMS,
|
||||||
OriginServerTS: gomatrixserverlib.AsTimestamp(time.Now()),
|
OriginServerTS: gomatrixserverlib.AsTimestamp(time.Now()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,18 +24,20 @@ import (
|
||||||
// RoomserverProducer produces events for the roomserver to consume.
|
// RoomserverProducer produces events for the roomserver to consume.
|
||||||
type RoomserverProducer struct {
|
type RoomserverProducer struct {
|
||||||
InputAPI api.RoomserverInputAPI
|
InputAPI api.RoomserverInputAPI
|
||||||
|
QueryAPI api.RoomserverQueryAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRoomserverProducer creates a new RoomserverProducer
|
// NewRoomserverProducer creates a new RoomserverProducer
|
||||||
func NewRoomserverProducer(inputAPI api.RoomserverInputAPI) *RoomserverProducer {
|
func NewRoomserverProducer(inputAPI api.RoomserverInputAPI, queryAPI api.RoomserverQueryAPI) *RoomserverProducer {
|
||||||
return &RoomserverProducer{
|
return &RoomserverProducer{
|
||||||
InputAPI: inputAPI,
|
InputAPI: inputAPI,
|
||||||
|
QueryAPI: queryAPI,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendEvents writes the given events to the roomserver input log. The events are written with KindNew.
|
// SendEvents writes the given events to the roomserver input log. The events are written with KindNew.
|
||||||
func (c *RoomserverProducer) SendEvents(
|
func (c *RoomserverProducer) SendEvents(
|
||||||
ctx context.Context, events []gomatrixserverlib.Event, sendAsServer gomatrixserverlib.ServerName,
|
ctx context.Context, events []gomatrixserverlib.HeaderedEvent, sendAsServer gomatrixserverlib.ServerName,
|
||||||
txnID *api.TransactionID,
|
txnID *api.TransactionID,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
ires := make([]api.InputRoomEvent, len(events))
|
ires := make([]api.InputRoomEvent, len(events))
|
||||||
|
|
@ -54,20 +56,20 @@ func (c *RoomserverProducer) SendEvents(
|
||||||
// SendEventWithState writes an event with KindNew to the roomserver input log
|
// SendEventWithState writes an event with KindNew to the roomserver input log
|
||||||
// with the state at the event as KindOutlier before it.
|
// with the state at the event as KindOutlier before it.
|
||||||
func (c *RoomserverProducer) SendEventWithState(
|
func (c *RoomserverProducer) SendEventWithState(
|
||||||
ctx context.Context, state gomatrixserverlib.RespState, event gomatrixserverlib.Event,
|
ctx context.Context, state gomatrixserverlib.RespState, event gomatrixserverlib.HeaderedEvent,
|
||||||
) error {
|
) error {
|
||||||
outliers, err := state.Events()
|
outliers, err := state.Events()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ires := make([]api.InputRoomEvent, len(outliers)+1)
|
var ires []api.InputRoomEvent
|
||||||
for i, outlier := range outliers {
|
for _, outlier := range outliers {
|
||||||
ires[i] = api.InputRoomEvent{
|
ires = append(ires, api.InputRoomEvent{
|
||||||
Kind: api.KindOutlier,
|
Kind: api.KindOutlier,
|
||||||
Event: outlier,
|
Event: outlier.Headered(event.RoomVersion),
|
||||||
AuthEventIDs: outlier.AuthEventIDs(),
|
AuthEventIDs: outlier.AuthEventIDs(),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
stateEventIDs := make([]string, len(state.StateEvents))
|
stateEventIDs := make([]string, len(state.StateEvents))
|
||||||
|
|
@ -75,13 +77,13 @@ func (c *RoomserverProducer) SendEventWithState(
|
||||||
stateEventIDs[i] = state.StateEvents[i].EventID()
|
stateEventIDs[i] = state.StateEvents[i].EventID()
|
||||||
}
|
}
|
||||||
|
|
||||||
ires[len(outliers)] = api.InputRoomEvent{
|
ires = append(ires, api.InputRoomEvent{
|
||||||
Kind: api.KindNew,
|
Kind: api.KindNew,
|
||||||
Event: event,
|
Event: event,
|
||||||
AuthEventIDs: event.AuthEventIDs(),
|
AuthEventIDs: event.AuthEventIDs(),
|
||||||
HasState: true,
|
HasState: true,
|
||||||
StateEventIDs: stateEventIDs,
|
StateEventIDs: stateEventIDs,
|
||||||
}
|
})
|
||||||
|
|
||||||
_, err = c.SendInputRoomEvents(ctx, ires)
|
_, err = c.SendInputRoomEvents(ctx, ires)
|
||||||
return err
|
return err
|
||||||
|
|
@ -102,10 +104,15 @@ func (c *RoomserverProducer) SendInputRoomEvents(
|
||||||
// This should only be needed for invite events that occur outside of a known room.
|
// This should only be needed for invite events that occur outside of a known room.
|
||||||
// If we are in the room then the event should be sent using the SendEvents method.
|
// If we are in the room then the event should be sent using the SendEvents method.
|
||||||
func (c *RoomserverProducer) SendInvite(
|
func (c *RoomserverProducer) SendInvite(
|
||||||
ctx context.Context, inviteEvent gomatrixserverlib.Event,
|
ctx context.Context, inviteEvent gomatrixserverlib.HeaderedEvent,
|
||||||
|
inviteRoomState []gomatrixserverlib.InviteV2StrippedState,
|
||||||
) error {
|
) error {
|
||||||
request := api.InputRoomEventsRequest{
|
request := api.InputRoomEventsRequest{
|
||||||
InputInviteEvents: []api.InputInviteEvent{{Event: inviteEvent}},
|
InputInviteEvents: []api.InputInviteEvent{{
|
||||||
|
Event: inviteEvent,
|
||||||
|
InviteRoomState: inviteRoomState,
|
||||||
|
RoomVersion: inviteEvent.RoomVersion,
|
||||||
|
}},
|
||||||
}
|
}
|
||||||
var response api.InputRoomEventsResponse
|
var response api.InputRoomEventsResponse
|
||||||
return c.InputAPI.InputRoomEvents(ctx, &request, &response)
|
return c.InputAPI.InputRoomEvents(ctx, &request, &response)
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,12 @@
|
||||||
package routing
|
package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
@ -30,7 +30,7 @@ import (
|
||||||
|
|
||||||
// GetAccountData implements GET /user/{userId}/[rooms/{roomid}/]account_data/{type}
|
// GetAccountData implements GET /user/{userId}/[rooms/{roomid}/]account_data/{type}
|
||||||
func GetAccountData(
|
func GetAccountData(
|
||||||
req *http.Request, accountDB *accounts.Database, device *authtypes.Device,
|
req *http.Request, accountDB accounts.Database, device *authtypes.Device,
|
||||||
userID string, roomID string, dataType string,
|
userID string, roomID string, dataType string,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
if userID != device.UserID {
|
if userID != device.UserID {
|
||||||
|
|
@ -42,7 +42,8 @@ func GetAccountData(
|
||||||
|
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if data, err := accountDB.GetAccountDataByType(
|
if data, err := accountDB.GetAccountDataByType(
|
||||||
|
|
@ -62,7 +63,7 @@ func GetAccountData(
|
||||||
|
|
||||||
// SaveAccountData implements PUT /user/{userId}/[rooms/{roomId}/]account_data/{type}
|
// SaveAccountData implements PUT /user/{userId}/[rooms/{roomId}/]account_data/{type}
|
||||||
func SaveAccountData(
|
func SaveAccountData(
|
||||||
req *http.Request, accountDB *accounts.Database, device *authtypes.Device,
|
req *http.Request, accountDB accounts.Database, device *authtypes.Device,
|
||||||
userID string, roomID string, dataType string, syncProducer *producers.SyncAPIProducer,
|
userID string, roomID string, dataType string, syncProducer *producers.SyncAPIProducer,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
if userID != device.UserID {
|
if userID != device.UserID {
|
||||||
|
|
@ -74,24 +75,42 @@ func SaveAccountData(
|
||||||
|
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
defer req.Body.Close() // nolint: errcheck
|
defer req.Body.Close() // nolint: errcheck
|
||||||
|
|
||||||
|
if req.Body == http.NoBody {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.NotJSON("Content not JSON"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(req.Body)
|
body, err := ioutil.ReadAll(req.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("ioutil.ReadAll failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !json.Valid(body) {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.BadJSON("Bad JSON content"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := accountDB.SaveAccountData(
|
if err := accountDB.SaveAccountData(
|
||||||
req.Context(), localpart, roomID, dataType, string(body),
|
req.Context(), localpart, roomID, dataType, string(body),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("accountDB.SaveAccountData failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := syncProducer.SendData(userID, roomID, dataType); err != nil {
|
if err := syncProducer.SendData(userID, roomID, dataType); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("syncProducer.SendData failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"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/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
@ -102,7 +101,7 @@ func serveTemplate(w http.ResponseWriter, templateHTML string, data map[string]s
|
||||||
// AuthFallback implements GET and POST /auth/{authType}/fallback/web?session={sessionID}
|
// AuthFallback implements GET and POST /auth/{authType}/fallback/web?session={sessionID}
|
||||||
func AuthFallback(
|
func AuthFallback(
|
||||||
w http.ResponseWriter, req *http.Request, authType string,
|
w http.ResponseWriter, req *http.Request, authType string,
|
||||||
cfg config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
) *util.JSONResponse {
|
) *util.JSONResponse {
|
||||||
sessionID := req.URL.Query().Get("session")
|
sessionID := req.URL.Query().Get("session")
|
||||||
|
|
||||||
|
|
@ -130,7 +129,7 @@ func AuthFallback(
|
||||||
if req.Method == http.MethodGet {
|
if req.Method == http.MethodGet {
|
||||||
// Handle Recaptcha
|
// Handle Recaptcha
|
||||||
if authType == authtypes.LoginTypeRecaptcha {
|
if authType == authtypes.LoginTypeRecaptcha {
|
||||||
if err := checkRecaptchaEnabled(&cfg, w, req); err != nil {
|
if err := checkRecaptchaEnabled(cfg, w, req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,19 +143,20 @@ func AuthFallback(
|
||||||
} else if req.Method == http.MethodPost {
|
} else if req.Method == http.MethodPost {
|
||||||
// Handle Recaptcha
|
// Handle Recaptcha
|
||||||
if authType == authtypes.LoginTypeRecaptcha {
|
if authType == authtypes.LoginTypeRecaptcha {
|
||||||
if err := checkRecaptchaEnabled(&cfg, w, req); err != nil {
|
if err := checkRecaptchaEnabled(cfg, w, req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
clientIP := req.RemoteAddr
|
clientIP := req.RemoteAddr
|
||||||
err := req.ParseForm()
|
err := req.ParseForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res := httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("req.ParseForm failed")
|
||||||
|
res := jsonerror.InternalServerError()
|
||||||
return &res
|
return &res
|
||||||
}
|
}
|
||||||
|
|
||||||
response := req.Form.Get("g-recaptcha-response")
|
response := req.Form.Get("g-recaptcha-response")
|
||||||
if err := validateRecaptcha(&cfg, response, clientIP); err != nil {
|
if err := validateRecaptcha(cfg, response, clientIP); err != nil {
|
||||||
util.GetLogger(req.Context()).Error(err)
|
util.GetLogger(req.Context()).Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -203,7 +203,8 @@ func writeHTTPMessage(
|
||||||
w.WriteHeader(header)
|
w.WriteHeader(header)
|
||||||
_, err := w.Write([]byte(message))
|
_, err := w.Write([]byte(message))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res := httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("w.Write failed")
|
||||||
|
res := jsonerror.InternalServerError()
|
||||||
return &res
|
return &res
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ package routing
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
@ -29,13 +29,14 @@ func GetCapabilities(
|
||||||
req *http.Request, queryAPI roomserverAPI.RoomserverQueryAPI,
|
req *http.Request, queryAPI roomserverAPI.RoomserverQueryAPI,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
roomVersionsQueryReq := roomserverAPI.QueryRoomVersionCapabilitiesRequest{}
|
roomVersionsQueryReq := roomserverAPI.QueryRoomVersionCapabilitiesRequest{}
|
||||||
var roomVersionsQueryRes roomserverAPI.QueryRoomVersionCapabilitiesResponse
|
roomVersionsQueryRes := roomserverAPI.QueryRoomVersionCapabilitiesResponse{}
|
||||||
if err := queryAPI.QueryRoomVersionCapabilities(
|
if err := queryAPI.QueryRoomVersionCapabilities(
|
||||||
req.Context(),
|
req.Context(),
|
||||||
&roomVersionsQueryReq,
|
&roomVersionsQueryReq,
|
||||||
&roomVersionsQueryRes,
|
&roomVersionsQueryRes,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("queryAPI.QueryRoomVersionCapabilities failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
response := map[string]interface{}{
|
response := map[string]interface{}{
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
|
|
||||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
roomserverVersion "github.com/matrix-org/dendrite/roomserver/version"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
||||||
|
|
@ -47,6 +48,7 @@ type createRoomRequest struct {
|
||||||
InitialState []fledglingEvent `json:"initial_state"`
|
InitialState []fledglingEvent `json:"initial_state"`
|
||||||
RoomAliasName string `json:"room_alias_name"`
|
RoomAliasName string `json:"room_alias_name"`
|
||||||
GuestCanJoin bool `json:"guest_can_join"`
|
GuestCanJoin bool `json:"guest_can_join"`
|
||||||
|
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -134,8 +136,8 @@ type fledglingEvent struct {
|
||||||
// CreateRoom implements /createRoom
|
// CreateRoom implements /createRoom
|
||||||
func CreateRoom(
|
func CreateRoom(
|
||||||
req *http.Request, device *authtypes.Device,
|
req *http.Request, device *authtypes.Device,
|
||||||
cfg config.Dendrite, producer *producers.RoomserverProducer,
|
cfg *config.Dendrite, producer *producers.RoomserverProducer,
|
||||||
accountDB *accounts.Database, aliasAPI roomserverAPI.RoomserverAliasAPI,
|
accountDB accounts.Database, aliasAPI roomserverAPI.RoomserverAliasAPI,
|
||||||
asAPI appserviceAPI.AppServiceQueryAPI,
|
asAPI appserviceAPI.AppServiceQueryAPI,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
// TODO (#267): Check room ID doesn't clash with an existing one, and we
|
// TODO (#267): Check room ID doesn't clash with an existing one, and we
|
||||||
|
|
@ -148,8 +150,8 @@ func CreateRoom(
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
func createRoom(
|
func createRoom(
|
||||||
req *http.Request, device *authtypes.Device,
|
req *http.Request, device *authtypes.Device,
|
||||||
cfg config.Dendrite, roomID string, producer *producers.RoomserverProducer,
|
cfg *config.Dendrite, roomID string, producer *producers.RoomserverProducer,
|
||||||
accountDB *accounts.Database, aliasAPI roomserverAPI.RoomserverAliasAPI,
|
accountDB accounts.Database, aliasAPI roomserverAPI.RoomserverAliasAPI,
|
||||||
asAPI appserviceAPI.AppServiceQueryAPI,
|
asAPI appserviceAPI.AppServiceQueryAPI,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
logger := util.GetLogger(req.Context())
|
logger := util.GetLogger(req.Context())
|
||||||
|
|
@ -180,7 +182,19 @@ func createRoom(
|
||||||
}
|
}
|
||||||
|
|
||||||
r.CreationContent["creator"] = userID
|
r.CreationContent["creator"] = userID
|
||||||
r.CreationContent["room_version"] = "1" // TODO: We set this to 1 before we support Room versioning
|
roomVersion := roomserverVersion.DefaultRoomVersion()
|
||||||
|
if r.RoomVersion != "" {
|
||||||
|
candidateVersion := gomatrixserverlib.RoomVersion(r.RoomVersion)
|
||||||
|
_, roomVersionError := roomserverVersion.SupportedRoomVersion(candidateVersion)
|
||||||
|
if roomVersionError != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.UnsupportedRoomVersion(roomVersionError.Error()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
roomVersion = candidateVersion
|
||||||
|
}
|
||||||
|
r.CreationContent["room_version"] = roomVersion
|
||||||
|
|
||||||
// TODO: visibility/presets/raw initial state
|
// TODO: visibility/presets/raw initial state
|
||||||
// TODO: Create room alias association
|
// TODO: Create room alias association
|
||||||
|
|
@ -189,11 +203,13 @@ func createRoom(
|
||||||
logger.WithFields(log.Fields{
|
logger.WithFields(log.Fields{
|
||||||
"userID": userID,
|
"userID": userID,
|
||||||
"roomID": roomID,
|
"roomID": roomID,
|
||||||
|
"roomVersion": r.CreationContent["room_version"],
|
||||||
}).Info("Creating new room")
|
}).Info("Creating new room")
|
||||||
|
|
||||||
profile, err := appserviceAPI.RetrieveUserProfile(req.Context(), userID, asAPI, accountDB)
|
profile, err := appserviceAPI.RetrieveUserProfile(req.Context(), userID, asAPI, accountDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("appserviceAPI.RetrieveUserProfile failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
membershipContent := gomatrixserverlib.MemberContent{
|
membershipContent := gomatrixserverlib.MemberContent{
|
||||||
|
|
@ -221,7 +237,7 @@ func createRoom(
|
||||||
historyVisibility = historyVisibilityShared
|
historyVisibility = historyVisibilityShared
|
||||||
}
|
}
|
||||||
|
|
||||||
var builtEvents []gomatrixserverlib.Event
|
var builtEvents []gomatrixserverlib.HeaderedEvent
|
||||||
|
|
||||||
// send events into the room in order of:
|
// send events into the room in order of:
|
||||||
// 1- m.room.create
|
// 1- m.room.create
|
||||||
|
|
@ -276,33 +292,38 @@ func createRoom(
|
||||||
}
|
}
|
||||||
err = builder.SetContent(e.Content)
|
err = builder.SetContent(e.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("builder.SetContent failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
builder.PrevEvents = []gomatrixserverlib.EventReference{builtEvents[i-1].EventReference()}
|
builder.PrevEvents = []gomatrixserverlib.EventReference{builtEvents[i-1].EventReference()}
|
||||||
}
|
}
|
||||||
var ev *gomatrixserverlib.Event
|
var ev *gomatrixserverlib.Event
|
||||||
ev, err = buildEvent(&builder, &authEvents, cfg, evTime)
|
ev, err = buildEvent(&builder, &authEvents, cfg, evTime, roomVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("buildEvent failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = gomatrixserverlib.Allowed(*ev, &authEvents); err != nil {
|
if err = gomatrixserverlib.Allowed(*ev, &authEvents); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.Allowed failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the event to the list of auth events
|
// Add the event to the list of auth events
|
||||||
builtEvents = append(builtEvents, *ev)
|
builtEvents = append(builtEvents, (*ev).Headered(roomVersion))
|
||||||
err = authEvents.AddEvent(ev)
|
err = authEvents.AddEvent(ev)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("authEvents.AddEvent failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// send events to the room server
|
// send events to the room server
|
||||||
_, err = producer.SendEvents(req.Context(), builtEvents, cfg.Matrix.ServerName, nil)
|
_, err = producer.SendEvents(req.Context(), builtEvents, cfg.Matrix.ServerName, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("producer.SendEvents failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(#269): Reserve room alias while we create the room. This stops us
|
// TODO(#269): Reserve room alias while we create the room. This stops us
|
||||||
|
|
@ -321,7 +342,8 @@ func createRoom(
|
||||||
var aliasResp roomserverAPI.SetRoomAliasResponse
|
var aliasResp roomserverAPI.SetRoomAliasResponse
|
||||||
err = aliasAPI.SetRoomAlias(req.Context(), &aliasReq, &aliasResp)
|
err = aliasAPI.SetRoomAlias(req.Context(), &aliasReq, &aliasResp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("aliasAPI.SetRoomAlias failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if aliasResp.AliasExists {
|
if aliasResp.AliasExists {
|
||||||
|
|
@ -344,8 +366,9 @@ func createRoom(
|
||||||
func buildEvent(
|
func buildEvent(
|
||||||
builder *gomatrixserverlib.EventBuilder,
|
builder *gomatrixserverlib.EventBuilder,
|
||||||
provider gomatrixserverlib.AuthEventProvider,
|
provider gomatrixserverlib.AuthEventProvider,
|
||||||
cfg config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
evTime time.Time,
|
evTime time.Time,
|
||||||
|
roomVersion gomatrixserverlib.RoomVersion,
|
||||||
) (*gomatrixserverlib.Event, error) {
|
) (*gomatrixserverlib.Event, error) {
|
||||||
eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(builder)
|
eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(builder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -356,10 +379,12 @@ func buildEvent(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
builder.AuthEvents = refs
|
builder.AuthEvents = refs
|
||||||
eventID := fmt.Sprintf("$%s:%s", util.RandomString(16), cfg.Matrix.ServerName)
|
event, err := builder.Build(
|
||||||
event, err := builder.Build(eventID, evTime, cfg.Matrix.ServerName, cfg.Matrix.KeyID, cfg.Matrix.PrivateKey)
|
evTime, cfg.Matrix.ServerName, cfg.Matrix.KeyID,
|
||||||
|
cfg.Matrix.PrivateKey, roomVersion,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot build event %s : Builder failed to build. %s", builder.Type, err)
|
return nil, fmt.Errorf("cannot build event %s : Builder failed to build. %w", builder.Type, err)
|
||||||
}
|
}
|
||||||
return &event, nil
|
return &event, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
||||||
"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/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
@ -40,14 +39,19 @@ type deviceUpdateJSON struct {
|
||||||
DisplayName *string `json:"display_name"`
|
DisplayName *string `json:"display_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type devicesDeleteJSON struct {
|
||||||
|
Devices []string `json:"devices"`
|
||||||
|
}
|
||||||
|
|
||||||
// GetDeviceByID handles /devices/{deviceID}
|
// GetDeviceByID handles /devices/{deviceID}
|
||||||
func GetDeviceByID(
|
func GetDeviceByID(
|
||||||
req *http.Request, deviceDB *devices.Database, device *authtypes.Device,
|
req *http.Request, deviceDB devices.Database, device *authtypes.Device,
|
||||||
deviceID string,
|
deviceID string,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
|
|
@ -58,7 +62,8 @@ func GetDeviceByID(
|
||||||
JSON: jsonerror.NotFound("Unknown device"),
|
JSON: jsonerror.NotFound("Unknown device"),
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("deviceDB.GetDeviceByID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -72,18 +77,20 @@ func GetDeviceByID(
|
||||||
|
|
||||||
// GetDevicesByLocalpart handles /devices
|
// GetDevicesByLocalpart handles /devices
|
||||||
func GetDevicesByLocalpart(
|
func GetDevicesByLocalpart(
|
||||||
req *http.Request, deviceDB *devices.Database, device *authtypes.Device,
|
req *http.Request, deviceDB devices.Database, device *authtypes.Device,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
deviceList, err := deviceDB.GetDevicesByLocalpart(ctx, localpart)
|
deviceList, err := deviceDB.GetDevicesByLocalpart(ctx, localpart)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("deviceDB.GetDevicesByLocalpart failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
res := devicesJSON{}
|
res := devicesJSON{}
|
||||||
|
|
@ -103,12 +110,13 @@ func GetDevicesByLocalpart(
|
||||||
|
|
||||||
// UpdateDeviceByID handles PUT on /devices/{deviceID}
|
// UpdateDeviceByID handles PUT on /devices/{deviceID}
|
||||||
func UpdateDeviceByID(
|
func UpdateDeviceByID(
|
||||||
req *http.Request, deviceDB *devices.Database, device *authtypes.Device,
|
req *http.Request, deviceDB devices.Database, device *authtypes.Device,
|
||||||
deviceID string,
|
deviceID string,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
|
|
@ -119,7 +127,8 @@ func UpdateDeviceByID(
|
||||||
JSON: jsonerror.NotFound("Unknown device"),
|
JSON: jsonerror.NotFound("Unknown device"),
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("deviceDB.GetDeviceByID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if dev.UserID != device.UserID {
|
if dev.UserID != device.UserID {
|
||||||
|
|
@ -134,11 +143,69 @@ func UpdateDeviceByID(
|
||||||
payload := deviceUpdateJSON{}
|
payload := deviceUpdateJSON{}
|
||||||
|
|
||||||
if err := json.NewDecoder(req.Body).Decode(&payload); err != nil {
|
if err := json.NewDecoder(req.Body).Decode(&payload); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("json.NewDecoder.Decode failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := deviceDB.UpdateDevice(ctx, localpart, deviceID, payload.DisplayName); err != nil {
|
if err := deviceDB.UpdateDevice(ctx, localpart, deviceID, payload.DisplayName); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("deviceDB.UpdateDevice failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: struct{}{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDeviceById handles DELETE requests to /devices/{deviceId}
|
||||||
|
func DeleteDeviceById(
|
||||||
|
req *http.Request, deviceDB devices.Database, device *authtypes.Device,
|
||||||
|
deviceID string,
|
||||||
|
) util.JSONResponse {
|
||||||
|
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
|
}
|
||||||
|
ctx := req.Context()
|
||||||
|
|
||||||
|
defer req.Body.Close() // nolint: errcheck
|
||||||
|
|
||||||
|
if err := deviceDB.RemoveDevice(ctx, deviceID, localpart); err != nil {
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Error("deviceDB.RemoveDevice failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: struct{}{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDevices handles POST requests to /delete_devices
|
||||||
|
func DeleteDevices(
|
||||||
|
req *http.Request, deviceDB devices.Database, device *authtypes.Device,
|
||||||
|
) util.JSONResponse {
|
||||||
|
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := req.Context()
|
||||||
|
payload := devicesDeleteJSON{}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(req.Body).Decode(&payload); err != nil {
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Error("json.NewDecoder.Decode failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
|
}
|
||||||
|
|
||||||
|
defer req.Body.Close() // nolint: errcheck
|
||||||
|
|
||||||
|
if err := deviceDB.RemoveDevices(ctx, localpart, payload.Devices); err != nil {
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Error("deviceDB.RemoveDevices failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,8 @@ func DirectoryRoom(
|
||||||
queryReq := roomserverAPI.GetRoomIDForAliasRequest{Alias: roomAlias}
|
queryReq := roomserverAPI.GetRoomIDForAliasRequest{Alias: roomAlias}
|
||||||
var queryRes roomserverAPI.GetRoomIDForAliasResponse
|
var queryRes roomserverAPI.GetRoomIDForAliasResponse
|
||||||
if err = rsAPI.GetRoomIDForAlias(req.Context(), &queryReq, &queryRes); err != nil {
|
if err = rsAPI.GetRoomIDForAlias(req.Context(), &queryReq, &queryRes); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("rsAPI.GetRoomIDForAlias failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
res.RoomID = queryRes.RoomID
|
res.RoomID = queryRes.RoomID
|
||||||
|
|
@ -76,7 +77,8 @@ func DirectoryRoom(
|
||||||
if fedErr != nil {
|
if fedErr != nil {
|
||||||
// TODO: Return 502 if the remote server errored.
|
// TODO: Return 502 if the remote server errored.
|
||||||
// TODO: Return 504 if the remote server timed out.
|
// TODO: Return 504 if the remote server timed out.
|
||||||
return httputil.LogThenError(req, fedErr)
|
util.GetLogger(req.Context()).WithError(err).Error("federation.LookupRoomAlias failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
res.RoomID = fedRes.RoomID
|
res.RoomID = fedRes.RoomID
|
||||||
res.fillServers(fedRes.Servers)
|
res.fillServers(fedRes.Servers)
|
||||||
|
|
@ -94,7 +96,8 @@ func DirectoryRoom(
|
||||||
joinedHostsReq := federationSenderAPI.QueryJoinedHostServerNamesInRoomRequest{RoomID: res.RoomID}
|
joinedHostsReq := federationSenderAPI.QueryJoinedHostServerNamesInRoomRequest{RoomID: res.RoomID}
|
||||||
var joinedHostsRes federationSenderAPI.QueryJoinedHostServerNamesInRoomResponse
|
var joinedHostsRes federationSenderAPI.QueryJoinedHostServerNamesInRoomResponse
|
||||||
if err = fedSenderAPI.QueryJoinedHostServerNamesInRoom(req.Context(), &joinedHostsReq, &joinedHostsRes); err != nil {
|
if err = fedSenderAPI.QueryJoinedHostServerNamesInRoom(req.Context(), &joinedHostsReq, &joinedHostsRes); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("fedSenderAPI.QueryJoinedHostServerNamesInRoom failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
res.fillServers(joinedHostsRes.ServerNames)
|
res.fillServers(joinedHostsRes.ServerNames)
|
||||||
}
|
}
|
||||||
|
|
@ -165,7 +168,8 @@ func SetLocalAlias(
|
||||||
}
|
}
|
||||||
var queryRes roomserverAPI.SetRoomAliasResponse
|
var queryRes roomserverAPI.SetRoomAliasResponse
|
||||||
if err := aliasAPI.SetRoomAlias(req.Context(), &queryReq, &queryRes); err != nil {
|
if err := aliasAPI.SetRoomAlias(req.Context(), &queryReq, &queryRes); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("aliasAPI.SetRoomAlias failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if queryRes.AliasExists {
|
if queryRes.AliasExists {
|
||||||
|
|
@ -194,7 +198,8 @@ func RemoveLocalAlias(
|
||||||
}
|
}
|
||||||
var creatorQueryRes roomserverAPI.GetCreatorIDForAliasResponse
|
var creatorQueryRes roomserverAPI.GetCreatorIDForAliasResponse
|
||||||
if err := aliasAPI.GetCreatorIDForAlias(req.Context(), &creatorQueryReq, &creatorQueryRes); err != nil {
|
if err := aliasAPI.GetCreatorIDForAlias(req.Context(), &creatorQueryReq, &creatorQueryRes); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("aliasAPI.GetCreatorIDForAlias failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if creatorQueryRes.UserID == "" {
|
if creatorQueryRes.UserID == "" {
|
||||||
|
|
@ -218,7 +223,8 @@ func RemoveLocalAlias(
|
||||||
}
|
}
|
||||||
var queryRes roomserverAPI.RemoveRoomAliasResponse
|
var queryRes roomserverAPI.RemoveRoomAliasResponse
|
||||||
if err := aliasAPI.RemoveRoomAlias(req.Context(), &queryReq, &queryRes); err != nil {
|
if err := aliasAPI.RemoveRoomAlias(req.Context(), &queryReq, &queryRes); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("aliasAPI.RemoveRoomAlias failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import (
|
||||||
|
|
||||||
// GetFilter implements GET /_matrix/client/r0/user/{userId}/filter/{filterId}
|
// GetFilter implements GET /_matrix/client/r0/user/{userId}/filter/{filterId}
|
||||||
func GetFilter(
|
func GetFilter(
|
||||||
req *http.Request, device *authtypes.Device, accountDB *accounts.Database, userID string, filterID string,
|
req *http.Request, device *authtypes.Device, accountDB accounts.Database, userID string, filterID string,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
if userID != device.UserID {
|
if userID != device.UserID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -37,7 +37,8 @@ func GetFilter(
|
||||||
}
|
}
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
filter, err := accountDB.GetFilter(req.Context(), localpart, filterID)
|
filter, err := accountDB.GetFilter(req.Context(), localpart, filterID)
|
||||||
|
|
@ -63,7 +64,7 @@ type filterResponse struct {
|
||||||
|
|
||||||
//PutFilter implements POST /_matrix/client/r0/user/{userId}/filter
|
//PutFilter implements POST /_matrix/client/r0/user/{userId}/filter
|
||||||
func PutFilter(
|
func PutFilter(
|
||||||
req *http.Request, device *authtypes.Device, accountDB *accounts.Database, userID string,
|
req *http.Request, device *authtypes.Device, accountDB accounts.Database, userID string,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
if userID != device.UserID {
|
if userID != device.UserID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -74,7 +75,8 @@ func PutFilter(
|
||||||
|
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
var filter gomatrixserverlib.Filter
|
var filter gomatrixserverlib.Filter
|
||||||
|
|
@ -93,7 +95,8 @@ func PutFilter(
|
||||||
|
|
||||||
filterID, err := accountDB.PutFilter(req.Context(), localpart, &filter)
|
filterID, err := accountDB.PutFilter(req.Context(), localpart, &filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("accountDB.PutFilter failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"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/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
|
@ -31,7 +30,7 @@ type getEventRequest struct {
|
||||||
device *authtypes.Device
|
device *authtypes.Device
|
||||||
roomID string
|
roomID string
|
||||||
eventID string
|
eventID string
|
||||||
cfg config.Dendrite
|
cfg *config.Dendrite
|
||||||
federation *gomatrixserverlib.FederationClient
|
federation *gomatrixserverlib.FederationClient
|
||||||
keyRing gomatrixserverlib.KeyRing
|
keyRing gomatrixserverlib.KeyRing
|
||||||
requestedEvent gomatrixserverlib.Event
|
requestedEvent gomatrixserverlib.Event
|
||||||
|
|
@ -44,7 +43,7 @@ func GetEvent(
|
||||||
device *authtypes.Device,
|
device *authtypes.Device,
|
||||||
roomID string,
|
roomID string,
|
||||||
eventID string,
|
eventID string,
|
||||||
cfg config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
queryAPI api.RoomserverQueryAPI,
|
queryAPI api.RoomserverQueryAPI,
|
||||||
federation *gomatrixserverlib.FederationClient,
|
federation *gomatrixserverlib.FederationClient,
|
||||||
keyRing gomatrixserverlib.KeyRing,
|
keyRing gomatrixserverlib.KeyRing,
|
||||||
|
|
@ -55,7 +54,8 @@ func GetEvent(
|
||||||
var eventsResp api.QueryEventsByIDResponse
|
var eventsResp api.QueryEventsByIDResponse
|
||||||
err := queryAPI.QueryEventsByID(req.Context(), &eventsReq, &eventsResp)
|
err := queryAPI.QueryEventsByID(req.Context(), &eventsReq, &eventsResp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("queryAPI.QueryEventsByID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(eventsResp.Events) == 0 {
|
if len(eventsResp.Events) == 0 {
|
||||||
|
|
@ -66,7 +66,7 @@ func GetEvent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
requestedEvent := eventsResp.Events[0]
|
requestedEvent := eventsResp.Events[0].Event
|
||||||
|
|
||||||
r := getEventRequest{
|
r := getEventRequest{
|
||||||
req: req,
|
req: req,
|
||||||
|
|
@ -89,7 +89,8 @@ func GetEvent(
|
||||||
}
|
}
|
||||||
var stateResp api.QueryStateAfterEventsResponse
|
var stateResp api.QueryStateAfterEventsResponse
|
||||||
if err := queryAPI.QueryStateAfterEvents(req.Context(), &stateReq, &stateResp); err != nil {
|
if err := queryAPI.QueryStateAfterEvents(req.Context(), &stateReq, &stateResp); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("queryAPI.QueryStateAfterEvents failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !stateResp.RoomExists {
|
if !stateResp.RoomExists {
|
||||||
|
|
@ -109,7 +110,8 @@ func GetEvent(
|
||||||
if stateEvent.StateKeyEquals(r.device.UserID) {
|
if stateEvent.StateKeyEquals(r.device.UserID) {
|
||||||
membership, err := stateEvent.Membership()
|
membership, err := stateEvent.Membership()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("stateEvent.Membership failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
if membership == gomatrixserverlib.Join {
|
if membership == gomatrixserverlib.Join {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/gomatrix"
|
"github.com/matrix-org/gomatrix"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
@ -39,13 +40,13 @@ func JoinRoomByIDOrAlias(
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
device *authtypes.Device,
|
device *authtypes.Device,
|
||||||
roomIDOrAlias string,
|
roomIDOrAlias string,
|
||||||
cfg config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
federation *gomatrixserverlib.FederationClient,
|
federation *gomatrixserverlib.FederationClient,
|
||||||
producer *producers.RoomserverProducer,
|
producer *producers.RoomserverProducer,
|
||||||
queryAPI roomserverAPI.RoomserverQueryAPI,
|
queryAPI roomserverAPI.RoomserverQueryAPI,
|
||||||
aliasAPI roomserverAPI.RoomserverAliasAPI,
|
aliasAPI roomserverAPI.RoomserverAliasAPI,
|
||||||
keyRing gomatrixserverlib.KeyRing,
|
keyRing gomatrixserverlib.KeyRing,
|
||||||
accountDB *accounts.Database,
|
accountDB accounts.Database,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
var content map[string]interface{} // must be a JSON object
|
var content map[string]interface{} // must be a JSON object
|
||||||
if resErr := httputil.UnmarshalJSONRequest(req, &content); resErr != nil {
|
if resErr := httputil.UnmarshalJSONRequest(req, &content); resErr != nil {
|
||||||
|
|
@ -62,12 +63,14 @@ func JoinRoomByIDOrAlias(
|
||||||
|
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
profile, err := accountDB.GetProfileByLocalpart(req.Context(), localpart)
|
profile, err := accountDB.GetProfileByLocalpart(req.Context(), localpart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("accountDB.GetProfileByLocalpart failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
content["membership"] = gomatrixserverlib.Join
|
content["membership"] = gomatrixserverlib.Join
|
||||||
|
|
@ -98,7 +101,7 @@ type joinRoomReq struct {
|
||||||
evTime time.Time
|
evTime time.Time
|
||||||
content map[string]interface{}
|
content map[string]interface{}
|
||||||
userID string
|
userID string
|
||||||
cfg config.Dendrite
|
cfg *config.Dendrite
|
||||||
federation *gomatrixserverlib.FederationClient
|
federation *gomatrixserverlib.FederationClient
|
||||||
producer *producers.RoomserverProducer
|
producer *producers.RoomserverProducer
|
||||||
queryAPI roomserverAPI.RoomserverQueryAPI
|
queryAPI roomserverAPI.RoomserverQueryAPI
|
||||||
|
|
@ -119,7 +122,8 @@ func (r joinRoomReq) joinRoomByID(roomID string) util.JSONResponse {
|
||||||
}
|
}
|
||||||
var queryRes roomserverAPI.QueryInvitesForUserResponse
|
var queryRes roomserverAPI.QueryInvitesForUserResponse
|
||||||
if err := r.queryAPI.QueryInvitesForUser(r.req.Context(), &queryReq, &queryRes); err != nil {
|
if err := r.queryAPI.QueryInvitesForUser(r.req.Context(), &queryReq, &queryRes); err != nil {
|
||||||
return httputil.LogThenError(r.req, err)
|
util.GetLogger(r.req.Context()).WithError(err).Error("r.queryAPI.QueryInvitesForUser failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
servers := []gomatrixserverlib.ServerName{}
|
servers := []gomatrixserverlib.ServerName{}
|
||||||
|
|
@ -127,7 +131,8 @@ func (r joinRoomReq) joinRoomByID(roomID string) util.JSONResponse {
|
||||||
for _, userID := range queryRes.InviteSenderUserIDs {
|
for _, userID := range queryRes.InviteSenderUserIDs {
|
||||||
_, domain, err := gomatrixserverlib.SplitID('@', userID)
|
_, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(r.req, err)
|
util.GetLogger(r.req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
if !seenInInviterIDs[domain] {
|
if !seenInInviterIDs[domain] {
|
||||||
servers = append(servers, domain)
|
servers = append(servers, domain)
|
||||||
|
|
@ -141,7 +146,8 @@ func (r joinRoomReq) joinRoomByID(roomID string) util.JSONResponse {
|
||||||
// Note: It's no guarantee we'll succeed because a room isn't bound to the domain in its ID
|
// Note: It's no guarantee we'll succeed because a room isn't bound to the domain in its ID
|
||||||
_, domain, err := gomatrixserverlib.SplitID('!', roomID)
|
_, domain, err := gomatrixserverlib.SplitID('!', roomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(r.req, err)
|
util.GetLogger(r.req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
if domain != r.cfg.Matrix.ServerName && !seenInInviterIDs[domain] {
|
if domain != r.cfg.Matrix.ServerName && !seenInInviterIDs[domain] {
|
||||||
servers = append(servers, domain)
|
servers = append(servers, domain)
|
||||||
|
|
@ -164,7 +170,8 @@ func (r joinRoomReq) joinRoomByAlias(roomAlias string) util.JSONResponse {
|
||||||
queryReq := roomserverAPI.GetRoomIDForAliasRequest{Alias: roomAlias}
|
queryReq := roomserverAPI.GetRoomIDForAliasRequest{Alias: roomAlias}
|
||||||
var queryRes roomserverAPI.GetRoomIDForAliasResponse
|
var queryRes roomserverAPI.GetRoomIDForAliasResponse
|
||||||
if err = r.aliasAPI.GetRoomIDForAlias(r.req.Context(), &queryReq, &queryRes); err != nil {
|
if err = r.aliasAPI.GetRoomIDForAlias(r.req.Context(), &queryReq, &queryRes); err != nil {
|
||||||
return httputil.LogThenError(r.req, err)
|
util.GetLogger(r.req.Context()).WithError(err).Error("r.aliasAPI.GetRoomIDForAlias failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(queryRes.RoomID) > 0 {
|
if len(queryRes.RoomID) > 0 {
|
||||||
|
|
@ -194,7 +201,8 @@ func (r joinRoomReq) joinRoomByRemoteAlias(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return httputil.LogThenError(r.req, err)
|
util.GetLogger(r.req.Context()).WithError(err).Error("r.federation.LookupRoomAlias failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.joinRoomUsingServers(resp.RoomID, resp.Servers)
|
return r.joinRoomUsingServers(resp.RoomID, resp.Servers)
|
||||||
|
|
@ -227,14 +235,26 @@ func (r joinRoomReq) joinRoomUsingServers(
|
||||||
var eb gomatrixserverlib.EventBuilder
|
var eb gomatrixserverlib.EventBuilder
|
||||||
err := r.writeToBuilder(&eb, roomID)
|
err := r.writeToBuilder(&eb, roomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(r.req, err)
|
util.GetLogger(r.req.Context()).WithError(err).Error("r.writeToBuilder failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
var queryRes roomserverAPI.QueryLatestEventsAndStateResponse
|
queryRes := roomserverAPI.QueryLatestEventsAndStateResponse{}
|
||||||
event, err := common.BuildEvent(r.req.Context(), &eb, r.cfg, r.evTime, r.queryAPI, &queryRes)
|
event, err := common.BuildEvent(r.req.Context(), &eb, r.cfg, r.evTime, r.queryAPI, &queryRes)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if _, err = r.producer.SendEvents(r.req.Context(), []gomatrixserverlib.Event{*event}, r.cfg.Matrix.ServerName, nil); err != nil {
|
// If we have successfully built an event at this point then we can
|
||||||
return httputil.LogThenError(r.req, err)
|
// assert that the room is a local room, as BuildEvent was able to
|
||||||
|
// add prev_events etc successfully.
|
||||||
|
if _, err = r.producer.SendEvents(
|
||||||
|
r.req.Context(),
|
||||||
|
[]gomatrixserverlib.HeaderedEvent{
|
||||||
|
(*event).Headered(queryRes.RoomVersion),
|
||||||
|
},
|
||||||
|
r.cfg.Matrix.ServerName,
|
||||||
|
nil,
|
||||||
|
); err != nil {
|
||||||
|
util.GetLogger(r.req.Context()).WithError(err).Error("r.producer.SendEvents failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
|
|
@ -243,8 +263,16 @@ func (r joinRoomReq) joinRoomUsingServers(
|
||||||
}{roomID},
|
}{roomID},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Otherwise, if we've reached here, then we haven't been able to populate
|
||||||
|
// prev_events etc for the room, therefore the room is probably federated.
|
||||||
|
|
||||||
|
// TODO: This needs to be re-thought, as in the case of an invite, the room
|
||||||
|
// will exist in the database in roomserver_rooms but won't have any state
|
||||||
|
// events, therefore this below check fails.
|
||||||
if err != common.ErrRoomNoExists {
|
if err != common.ErrRoomNoExists {
|
||||||
return httputil.LogThenError(r.req, err)
|
util.GetLogger(r.req.Context()).WithError(err).Error("common.BuildEvent failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(servers) == 0 {
|
if len(servers) == 0 {
|
||||||
|
|
@ -280,7 +308,8 @@ func (r joinRoomReq) joinRoomUsingServers(
|
||||||
// 4) We couldn't fetch the public keys needed to verify the
|
// 4) We couldn't fetch the public keys needed to verify the
|
||||||
// signatures on the state events.
|
// signatures on the state events.
|
||||||
// 5) ...
|
// 5) ...
|
||||||
return httputil.LogThenError(r.req, lastErr)
|
util.GetLogger(r.req.Context()).WithError(lastErr).Error("failed to join through any server")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
// joinRoomUsingServer tries to join a remote room using a given matrix server.
|
// joinRoomUsingServer tries to join a remote room using a given matrix server.
|
||||||
|
|
@ -288,42 +317,64 @@ func (r joinRoomReq) joinRoomUsingServers(
|
||||||
// server was invalid this returns an error.
|
// server was invalid this returns an error.
|
||||||
// Otherwise this returns a JSONResponse.
|
// Otherwise this returns a JSONResponse.
|
||||||
func (r joinRoomReq) joinRoomUsingServer(roomID string, server gomatrixserverlib.ServerName) (*util.JSONResponse, error) {
|
func (r joinRoomReq) joinRoomUsingServer(roomID string, server gomatrixserverlib.ServerName) (*util.JSONResponse, error) {
|
||||||
respMakeJoin, err := r.federation.MakeJoin(r.req.Context(), server, roomID, r.userID)
|
// Ask the room server for information about room versions.
|
||||||
|
var request api.QueryRoomVersionCapabilitiesRequest
|
||||||
|
var response api.QueryRoomVersionCapabilitiesResponse
|
||||||
|
if err := r.queryAPI.QueryRoomVersionCapabilities(r.req.Context(), &request, &response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var supportedVersions []gomatrixserverlib.RoomVersion
|
||||||
|
for version := range response.AvailableRoomVersions {
|
||||||
|
supportedVersions = append(supportedVersions, version)
|
||||||
|
}
|
||||||
|
respMakeJoin, err := r.federation.MakeJoin(r.req.Context(), server, roomID, r.userID, supportedVersions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: Check if the user was not allowed to join the room.
|
// TODO: Check if the user was not allowed to join the room.
|
||||||
return nil, err
|
return nil, fmt.Errorf("r.federation.MakeJoin: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set all the fields to be what they should be, this should be a no-op
|
// Set all the fields to be what they should be, this should be a no-op
|
||||||
// but it's possible that the remote server returned us something "odd"
|
// but it's possible that the remote server returned us something "odd"
|
||||||
err = r.writeToBuilder(&respMakeJoin.JoinEvent, roomID)
|
err = r.writeToBuilder(&respMakeJoin.JoinEvent, roomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("r.writeToBuilder: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if respMakeJoin.RoomVersion == "" {
|
||||||
|
respMakeJoin.RoomVersion = gomatrixserverlib.RoomVersionV1
|
||||||
|
}
|
||||||
|
if _, err = respMakeJoin.RoomVersion.EventFormat(); err != nil {
|
||||||
|
return &util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.UnsupportedRoomVersion(
|
||||||
|
fmt.Sprintf("Room version '%s' is not supported", respMakeJoin.RoomVersion),
|
||||||
|
),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
eventID := fmt.Sprintf("$%s:%s", util.RandomString(16), r.cfg.Matrix.ServerName)
|
|
||||||
event, err := respMakeJoin.JoinEvent.Build(
|
event, err := respMakeJoin.JoinEvent.Build(
|
||||||
eventID, r.evTime, r.cfg.Matrix.ServerName, r.cfg.Matrix.KeyID, r.cfg.Matrix.PrivateKey,
|
r.evTime, r.cfg.Matrix.ServerName, r.cfg.Matrix.KeyID,
|
||||||
|
r.cfg.Matrix.PrivateKey, respMakeJoin.RoomVersion,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res := httputil.LogThenError(r.req, err)
|
return nil, fmt.Errorf("respMakeJoin.JoinEvent.Build: %w", err)
|
||||||
return &res, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
respSendJoin, err := r.federation.SendJoin(r.req.Context(), server, event)
|
respSendJoin, err := r.federation.SendJoin(r.req.Context(), server, event, respMakeJoin.RoomVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("r.federation.SendJoin: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = respSendJoin.Check(r.req.Context(), r.keyRing, event); err != nil {
|
if err = respSendJoin.Check(r.req.Context(), r.keyRing, event); err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("respSendJoin: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = r.producer.SendEventWithState(
|
if err = r.producer.SendEventWithState(
|
||||||
r.req.Context(), gomatrixserverlib.RespState(respSendJoin.RespState), event,
|
r.req.Context(),
|
||||||
|
gomatrixserverlib.RespState(respSendJoin.RespState),
|
||||||
|
event.Headered(respMakeJoin.RoomVersion),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
res := httputil.LogThenError(r.req, err)
|
return nil, fmt.Errorf("r.producer.SendEventWithState: %w", err)
|
||||||
return &res, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &util.JSONResponse{
|
return &util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,8 @@ func passwordLogin() loginFlows {
|
||||||
|
|
||||||
// Login implements GET and POST /login
|
// Login implements GET and POST /login
|
||||||
func Login(
|
func Login(
|
||||||
req *http.Request, accountDB *accounts.Database, deviceDB *devices.Database,
|
req *http.Request, accountDB accounts.Database, deviceDB devices.Database,
|
||||||
cfg config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
if req.Method == http.MethodGet { // TODO: support other forms of login other than password, depending on config options
|
if req.Method == http.MethodGet { // TODO: support other forms of login other than password, depending on config options
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -122,7 +122,8 @@ func Login(
|
||||||
|
|
||||||
token, err := auth.GenerateAccessToken()
|
token, err := auth.GenerateAccessToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("auth.GenerateAccessToken failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
dev, err := getDevice(req.Context(), r, deviceDB, acc, token)
|
dev, err := getDevice(req.Context(), r, deviceDB, acc, token)
|
||||||
|
|
@ -153,7 +154,7 @@ func Login(
|
||||||
func getDevice(
|
func getDevice(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
r passwordRequest,
|
r passwordRequest,
|
||||||
deviceDB *devices.Database,
|
deviceDB devices.Database,
|
||||||
acc *authtypes.Account,
|
acc *authtypes.Account,
|
||||||
token string,
|
token string,
|
||||||
) (dev *authtypes.Device, err error) {
|
) (dev *authtypes.Device, err error) {
|
||||||
|
|
|
||||||
|
|
@ -19,22 +19,24 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Logout handles POST /logout
|
// Logout handles POST /logout
|
||||||
func Logout(
|
func Logout(
|
||||||
req *http.Request, deviceDB *devices.Database, device *authtypes.Device,
|
req *http.Request, deviceDB devices.Database, device *authtypes.Device,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := deviceDB.RemoveDevice(req.Context(), device.ID, localpart); err != nil {
|
if err := deviceDB.RemoveDevice(req.Context(), device.ID, localpart); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("deviceDB.RemoveDevice failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -45,15 +47,17 @@ func Logout(
|
||||||
|
|
||||||
// LogoutAll handles POST /logout/all
|
// LogoutAll handles POST /logout/all
|
||||||
func LogoutAll(
|
func LogoutAll(
|
||||||
req *http.Request, deviceDB *devices.Database, device *authtypes.Device,
|
req *http.Request, deviceDB devices.Database, device *authtypes.Device,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := deviceDB.RemoveAllDevices(req.Context(), localpart); err != nil {
|
if err := deviceDB.RemoveAllDevices(req.Context(), localpart); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("deviceDB.RemoveAllDevices failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/threepid"
|
"github.com/matrix-org/dendrite/clientapi/threepid"
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
||||||
|
|
@ -40,11 +41,20 @@ var errMissingUserID = errors.New("'user_id' must be supplied")
|
||||||
// SendMembership implements PUT /rooms/{roomID}/(join|kick|ban|unban|leave|invite)
|
// SendMembership implements PUT /rooms/{roomID}/(join|kick|ban|unban|leave|invite)
|
||||||
// by building a m.room.member event then sending it to the room server
|
// by building a m.room.member event then sending it to the room server
|
||||||
func SendMembership(
|
func SendMembership(
|
||||||
req *http.Request, accountDB *accounts.Database, device *authtypes.Device,
|
req *http.Request, accountDB accounts.Database, device *authtypes.Device,
|
||||||
roomID string, membership string, cfg config.Dendrite,
|
roomID string, membership string, cfg *config.Dendrite,
|
||||||
queryAPI roomserverAPI.RoomserverQueryAPI, asAPI appserviceAPI.AppServiceQueryAPI,
|
queryAPI roomserverAPI.RoomserverQueryAPI, asAPI appserviceAPI.AppServiceQueryAPI,
|
||||||
producer *producers.RoomserverProducer,
|
producer *producers.RoomserverProducer,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
|
verReq := api.QueryRoomVersionForRoomRequest{RoomID: roomID}
|
||||||
|
verRes := api.QueryRoomVersionForRoomResponse{}
|
||||||
|
if err := queryAPI.QueryRoomVersionForRoom(req.Context(), &verReq, &verRes); err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.UnsupportedRoomVersion(err.Error()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var body threepid.MembershipRequest
|
var body threepid.MembershipRequest
|
||||||
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
|
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
|
||||||
return *reqErr
|
return *reqErr
|
||||||
|
|
@ -90,13 +100,18 @@ func SendMembership(
|
||||||
JSON: jsonerror.NotFound(err.Error()),
|
JSON: jsonerror.NotFound(err.Error()),
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("buildMembershipEvent failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := producer.SendEvents(
|
if _, err := producer.SendEvents(
|
||||||
req.Context(), []gomatrixserverlib.Event{*event}, cfg.Matrix.ServerName, nil,
|
req.Context(),
|
||||||
|
[]gomatrixserverlib.HeaderedEvent{(*event).Headered(verRes.RoomVersion)},
|
||||||
|
cfg.Matrix.ServerName,
|
||||||
|
nil,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("producer.SendEvents failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
var returnData interface{} = struct{}{}
|
var returnData interface{} = struct{}{}
|
||||||
|
|
@ -116,10 +131,10 @@ func SendMembership(
|
||||||
|
|
||||||
func buildMembershipEvent(
|
func buildMembershipEvent(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
body threepid.MembershipRequest, accountDB *accounts.Database,
|
body threepid.MembershipRequest, accountDB accounts.Database,
|
||||||
device *authtypes.Device,
|
device *authtypes.Device,
|
||||||
membership, roomID string,
|
membership, roomID string,
|
||||||
cfg config.Dendrite, evTime time.Time,
|
cfg *config.Dendrite, evTime time.Time,
|
||||||
queryAPI roomserverAPI.RoomserverQueryAPI, asAPI appserviceAPI.AppServiceQueryAPI,
|
queryAPI roomserverAPI.RoomserverQueryAPI, asAPI appserviceAPI.AppServiceQueryAPI,
|
||||||
) (*gomatrixserverlib.Event, error) {
|
) (*gomatrixserverlib.Event, error) {
|
||||||
stateKey, reason, err := getMembershipStateKey(body, device, membership)
|
stateKey, reason, err := getMembershipStateKey(body, device, membership)
|
||||||
|
|
@ -165,8 +180,8 @@ func buildMembershipEvent(
|
||||||
func loadProfile(
|
func loadProfile(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
userID string,
|
userID string,
|
||||||
cfg config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
accountDB *accounts.Database,
|
accountDB accounts.Database,
|
||||||
asAPI appserviceAPI.AppServiceQueryAPI,
|
asAPI appserviceAPI.AppServiceQueryAPI,
|
||||||
) (*authtypes.Profile, error) {
|
) (*authtypes.Profile, error) {
|
||||||
_, serverName, err := gomatrixserverlib.SplitID('@', userID)
|
_, serverName, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
|
|
@ -214,9 +229,9 @@ func checkAndProcessThreepid(
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
device *authtypes.Device,
|
device *authtypes.Device,
|
||||||
body *threepid.MembershipRequest,
|
body *threepid.MembershipRequest,
|
||||||
cfg config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
queryAPI roomserverAPI.RoomserverQueryAPI,
|
queryAPI roomserverAPI.RoomserverQueryAPI,
|
||||||
accountDB *accounts.Database,
|
accountDB accounts.Database,
|
||||||
producer *producers.RoomserverProducer,
|
producer *producers.RoomserverProducer,
|
||||||
membership, roomID string,
|
membership, roomID string,
|
||||||
evTime time.Time,
|
evTime time.Time,
|
||||||
|
|
@ -242,7 +257,8 @@ func checkAndProcessThreepid(
|
||||||
JSON: jsonerror.NotFound(err.Error()),
|
JSON: jsonerror.NotFound(err.Error()),
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
er := httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("threepid.CheckAndProcessInvite failed")
|
||||||
|
er := jsonerror.InternalServerError()
|
||||||
return inviteStored, &er
|
return inviteStored, &er
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,9 @@ package routing
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"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/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
|
@ -26,14 +27,18 @@ import (
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type response struct {
|
type getMembershipResponse struct {
|
||||||
Chunk []gomatrixserverlib.ClientEvent `json:"chunk"`
|
Chunk []gomatrixserverlib.ClientEvent `json:"chunk"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type getJoinedRoomsResponse struct {
|
||||||
|
JoinedRooms []string `json:"joined_rooms"`
|
||||||
|
}
|
||||||
|
|
||||||
// GetMemberships implements GET /rooms/{roomId}/members
|
// GetMemberships implements GET /rooms/{roomId}/members
|
||||||
func GetMemberships(
|
func GetMemberships(
|
||||||
req *http.Request, device *authtypes.Device, roomID string, joinedOnly bool,
|
req *http.Request, device *authtypes.Device, roomID string, joinedOnly bool,
|
||||||
_ config.Dendrite,
|
_ *config.Dendrite,
|
||||||
queryAPI api.RoomserverQueryAPI,
|
queryAPI api.RoomserverQueryAPI,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
queryReq := api.QueryMembershipsForRoomRequest{
|
queryReq := api.QueryMembershipsForRoomRequest{
|
||||||
|
|
@ -43,7 +48,8 @@ func GetMemberships(
|
||||||
}
|
}
|
||||||
var queryRes api.QueryMembershipsForRoomResponse
|
var queryRes api.QueryMembershipsForRoomResponse
|
||||||
if err := queryAPI.QueryMembershipsForRoom(req.Context(), &queryReq, &queryRes); err != nil {
|
if err := queryAPI.QueryMembershipsForRoom(req.Context(), &queryReq, &queryRes); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("queryAPI.QueryMembershipsForRoom failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !queryRes.HasBeenInRoom {
|
if !queryRes.HasBeenInRoom {
|
||||||
|
|
@ -55,6 +61,27 @@ func GetMemberships(
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
JSON: response{queryRes.JoinEvents},
|
JSON: getMembershipResponse{queryRes.JoinEvents},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetJoinedRooms(
|
||||||
|
req *http.Request,
|
||||||
|
device *authtypes.Device,
|
||||||
|
accountsDB accounts.Database,
|
||||||
|
) util.JSONResponse {
|
||||||
|
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
|
}
|
||||||
|
joinedRooms, err := accountsDB.GetRoomIDsByLocalPart(req.Context(), localpart)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Error("accountsDB.GetRoomIDsByLocalPart failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
|
}
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: getJoinedRoomsResponse{joinedRooms},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ import (
|
||||||
|
|
||||||
// GetProfile implements GET /profile/{userID}
|
// GetProfile implements GET /profile/{userID}
|
||||||
func GetProfile(
|
func GetProfile(
|
||||||
req *http.Request, accountDB *accounts.Database, cfg *config.Dendrite,
|
req *http.Request, accountDB accounts.Database, cfg *config.Dendrite,
|
||||||
userID string,
|
userID string,
|
||||||
asAPI appserviceAPI.AppServiceQueryAPI,
|
asAPI appserviceAPI.AppServiceQueryAPI,
|
||||||
federation *gomatrixserverlib.FederationClient,
|
federation *gomatrixserverlib.FederationClient,
|
||||||
|
|
@ -50,7 +50,8 @@ func GetProfile(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("getProfile failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -64,7 +65,7 @@ func GetProfile(
|
||||||
|
|
||||||
// GetAvatarURL implements GET /profile/{userID}/avatar_url
|
// GetAvatarURL implements GET /profile/{userID}/avatar_url
|
||||||
func GetAvatarURL(
|
func GetAvatarURL(
|
||||||
req *http.Request, accountDB *accounts.Database, cfg *config.Dendrite,
|
req *http.Request, accountDB accounts.Database, cfg *config.Dendrite,
|
||||||
userID string, asAPI appserviceAPI.AppServiceQueryAPI,
|
userID string, asAPI appserviceAPI.AppServiceQueryAPI,
|
||||||
federation *gomatrixserverlib.FederationClient,
|
federation *gomatrixserverlib.FederationClient,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
|
|
@ -77,7 +78,8 @@ func GetAvatarURL(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("getProfile failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -90,7 +92,7 @@ func GetAvatarURL(
|
||||||
|
|
||||||
// SetAvatarURL implements PUT /profile/{userID}/avatar_url
|
// SetAvatarURL implements PUT /profile/{userID}/avatar_url
|
||||||
func SetAvatarURL(
|
func SetAvatarURL(
|
||||||
req *http.Request, accountDB *accounts.Database, device *authtypes.Device,
|
req *http.Request, accountDB accounts.Database, device *authtypes.Device,
|
||||||
userID string, producer *producers.UserUpdateProducer, cfg *config.Dendrite,
|
userID string, producer *producers.UserUpdateProducer, cfg *config.Dendrite,
|
||||||
rsProducer *producers.RoomserverProducer, queryAPI api.RoomserverQueryAPI,
|
rsProducer *producers.RoomserverProducer, queryAPI api.RoomserverQueryAPI,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
|
|
@ -116,7 +118,8 @@ func SetAvatarURL(
|
||||||
|
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
evTime, err := httputil.ParseTSParam(req)
|
evTime, err := httputil.ParseTSParam(req)
|
||||||
|
|
@ -129,16 +132,19 @@ func SetAvatarURL(
|
||||||
|
|
||||||
oldProfile, err := accountDB.GetProfileByLocalpart(req.Context(), localpart)
|
oldProfile, err := accountDB.GetProfileByLocalpart(req.Context(), localpart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("accountDB.GetProfileByLocalpart failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = accountDB.SetAvatarURL(req.Context(), localpart, r.AvatarURL); err != nil {
|
if err = accountDB.SetAvatarURL(req.Context(), localpart, r.AvatarURL); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("accountDB.SetAvatarURL failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
memberships, err := accountDB.GetMembershipsByLocalpart(req.Context(), localpart)
|
memberships, err := accountDB.GetMembershipsByLocalpart(req.Context(), localpart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("accountDB.GetMembershipsByLocalpart failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
newProfile := authtypes.Profile{
|
newProfile := authtypes.Profile{
|
||||||
|
|
@ -151,15 +157,18 @@ func SetAvatarURL(
|
||||||
req.Context(), memberships, newProfile, userID, cfg, evTime, queryAPI,
|
req.Context(), memberships, newProfile, userID, cfg, evTime, queryAPI,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("buildMembershipEvents failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := rsProducer.SendEvents(req.Context(), events, cfg.Matrix.ServerName, nil); err != nil {
|
if _, err := rsProducer.SendEvents(req.Context(), events, cfg.Matrix.ServerName, nil); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("rsProducer.SendEvents failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := producer.SendUpdate(userID, changedKey, oldProfile.AvatarURL, r.AvatarURL); err != nil {
|
if err := producer.SendUpdate(userID, changedKey, oldProfile.AvatarURL, r.AvatarURL); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("producer.SendUpdate failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -170,7 +179,7 @@ func SetAvatarURL(
|
||||||
|
|
||||||
// GetDisplayName implements GET /profile/{userID}/displayname
|
// GetDisplayName implements GET /profile/{userID}/displayname
|
||||||
func GetDisplayName(
|
func GetDisplayName(
|
||||||
req *http.Request, accountDB *accounts.Database, cfg *config.Dendrite,
|
req *http.Request, accountDB accounts.Database, cfg *config.Dendrite,
|
||||||
userID string, asAPI appserviceAPI.AppServiceQueryAPI,
|
userID string, asAPI appserviceAPI.AppServiceQueryAPI,
|
||||||
federation *gomatrixserverlib.FederationClient,
|
federation *gomatrixserverlib.FederationClient,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
|
|
@ -183,7 +192,8 @@ func GetDisplayName(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("getProfile failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -196,7 +206,7 @@ func GetDisplayName(
|
||||||
|
|
||||||
// SetDisplayName implements PUT /profile/{userID}/displayname
|
// SetDisplayName implements PUT /profile/{userID}/displayname
|
||||||
func SetDisplayName(
|
func SetDisplayName(
|
||||||
req *http.Request, accountDB *accounts.Database, device *authtypes.Device,
|
req *http.Request, accountDB accounts.Database, device *authtypes.Device,
|
||||||
userID string, producer *producers.UserUpdateProducer, cfg *config.Dendrite,
|
userID string, producer *producers.UserUpdateProducer, cfg *config.Dendrite,
|
||||||
rsProducer *producers.RoomserverProducer, queryAPI api.RoomserverQueryAPI,
|
rsProducer *producers.RoomserverProducer, queryAPI api.RoomserverQueryAPI,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
|
|
@ -222,7 +232,8 @@ func SetDisplayName(
|
||||||
|
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
evTime, err := httputil.ParseTSParam(req)
|
evTime, err := httputil.ParseTSParam(req)
|
||||||
|
|
@ -235,16 +246,19 @@ func SetDisplayName(
|
||||||
|
|
||||||
oldProfile, err := accountDB.GetProfileByLocalpart(req.Context(), localpart)
|
oldProfile, err := accountDB.GetProfileByLocalpart(req.Context(), localpart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("accountDB.GetProfileByLocalpart failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = accountDB.SetDisplayName(req.Context(), localpart, r.DisplayName); err != nil {
|
if err = accountDB.SetDisplayName(req.Context(), localpart, r.DisplayName); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("accountDB.SetDisplayName failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
memberships, err := accountDB.GetMembershipsByLocalpart(req.Context(), localpart)
|
memberships, err := accountDB.GetMembershipsByLocalpart(req.Context(), localpart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("accountDB.GetMembershipsByLocalpart failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
newProfile := authtypes.Profile{
|
newProfile := authtypes.Profile{
|
||||||
|
|
@ -257,15 +271,18 @@ func SetDisplayName(
|
||||||
req.Context(), memberships, newProfile, userID, cfg, evTime, queryAPI,
|
req.Context(), memberships, newProfile, userID, cfg, evTime, queryAPI,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("buildMembershipEvents failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := rsProducer.SendEvents(req.Context(), events, cfg.Matrix.ServerName, nil); err != nil {
|
if _, err := rsProducer.SendEvents(req.Context(), events, cfg.Matrix.ServerName, nil); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("rsProducer.SendEvents failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := producer.SendUpdate(userID, changedKey, oldProfile.DisplayName, r.DisplayName); err != nil {
|
if err := producer.SendUpdate(userID, changedKey, oldProfile.DisplayName, r.DisplayName); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("producer.SendUpdate failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -279,7 +296,7 @@ func SetDisplayName(
|
||||||
// Returns an error when something goes wrong or specifically
|
// Returns an error when something goes wrong or specifically
|
||||||
// common.ErrProfileNoExists when the profile doesn't exist.
|
// common.ErrProfileNoExists when the profile doesn't exist.
|
||||||
func getProfile(
|
func getProfile(
|
||||||
ctx context.Context, accountDB *accounts.Database, cfg *config.Dendrite,
|
ctx context.Context, accountDB accounts.Database, cfg *config.Dendrite,
|
||||||
userID string,
|
userID string,
|
||||||
asAPI appserviceAPI.AppServiceQueryAPI,
|
asAPI appserviceAPI.AppServiceQueryAPI,
|
||||||
federation *gomatrixserverlib.FederationClient,
|
federation *gomatrixserverlib.FederationClient,
|
||||||
|
|
@ -321,10 +338,16 @@ func buildMembershipEvents(
|
||||||
memberships []authtypes.Membership,
|
memberships []authtypes.Membership,
|
||||||
newProfile authtypes.Profile, userID string, cfg *config.Dendrite,
|
newProfile authtypes.Profile, userID string, cfg *config.Dendrite,
|
||||||
evTime time.Time, queryAPI api.RoomserverQueryAPI,
|
evTime time.Time, queryAPI api.RoomserverQueryAPI,
|
||||||
) ([]gomatrixserverlib.Event, error) {
|
) ([]gomatrixserverlib.HeaderedEvent, error) {
|
||||||
evs := []gomatrixserverlib.Event{}
|
evs := []gomatrixserverlib.HeaderedEvent{}
|
||||||
|
|
||||||
for _, membership := range memberships {
|
for _, membership := range memberships {
|
||||||
|
verReq := api.QueryRoomVersionForRoomRequest{RoomID: membership.RoomID}
|
||||||
|
verRes := api.QueryRoomVersionForRoomResponse{}
|
||||||
|
if err := queryAPI.QueryRoomVersionForRoom(ctx, &verReq, &verRes); err != nil {
|
||||||
|
return []gomatrixserverlib.HeaderedEvent{}, err
|
||||||
|
}
|
||||||
|
|
||||||
builder := gomatrixserverlib.EventBuilder{
|
builder := gomatrixserverlib.EventBuilder{
|
||||||
Sender: userID,
|
Sender: userID,
|
||||||
RoomID: membership.RoomID,
|
RoomID: membership.RoomID,
|
||||||
|
|
@ -343,12 +366,12 @@ func buildMembershipEvents(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
event, err := common.BuildEvent(ctx, &builder, *cfg, evTime, queryAPI, nil)
|
event, err := common.BuildEvent(ctx, &builder, cfg, evTime, queryAPI, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
evs = append(evs, *event)
|
evs = append(evs, (*event).Headered(verRes.RoomVersion))
|
||||||
}
|
}
|
||||||
|
|
||||||
return evs, nil
|
return evs, nil
|
||||||
|
|
|
||||||
|
|
@ -440,11 +440,10 @@ func validateApplicationService(
|
||||||
// http://matrix.org/speculator/spec/HEAD/client_server/unstable.html#post-matrix-client-unstable-register
|
// http://matrix.org/speculator/spec/HEAD/client_server/unstable.html#post-matrix-client-unstable-register
|
||||||
func Register(
|
func Register(
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
accountDB *accounts.Database,
|
accountDB accounts.Database,
|
||||||
deviceDB *devices.Database,
|
deviceDB devices.Database,
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
|
|
||||||
var r registerRequest
|
var r registerRequest
|
||||||
resErr := httputil.UnmarshalJSONRequest(req, &r)
|
resErr := httputil.UnmarshalJSONRequest(req, &r)
|
||||||
if resErr != nil {
|
if resErr != nil {
|
||||||
|
|
@ -472,7 +471,8 @@ func Register(
|
||||||
if r.Username == "" {
|
if r.Username == "" {
|
||||||
id, err := accountDB.GetNewNumericLocalpart(req.Context())
|
id, err := accountDB.GetNewNumericLocalpart(req.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("accountDB.GetNewNumericLocalpart failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Username = strconv.FormatInt(id, 10)
|
r.Username = strconv.FormatInt(id, 10)
|
||||||
|
|
@ -513,18 +513,10 @@ func handleGuestRegistration(
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
r registerRequest,
|
r registerRequest,
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
accountDB *accounts.Database,
|
accountDB accounts.Database,
|
||||||
deviceDB *devices.Database,
|
deviceDB devices.Database,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
|
acc, err := accountDB.CreateGuestAccount(req.Context())
|
||||||
//Generate numeric local part for guest user
|
|
||||||
id, err := accountDB.GetNewNumericLocalpart(req.Context())
|
|
||||||
if err != nil {
|
|
||||||
return httputil.LogThenError(req, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
localpart := strconv.FormatInt(id, 10)
|
|
||||||
acc, err := accountDB.CreateAccount(req.Context(), localpart, "", "")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
|
|
@ -570,8 +562,8 @@ func handleRegistrationFlow(
|
||||||
r registerRequest,
|
r registerRequest,
|
||||||
sessionID string,
|
sessionID string,
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
accountDB *accounts.Database,
|
accountDB accounts.Database,
|
||||||
deviceDB *devices.Database,
|
deviceDB devices.Database,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
// TODO: Shared secret registration (create new user scripts)
|
// TODO: Shared secret registration (create new user scripts)
|
||||||
// TODO: Enable registration config flag
|
// TODO: Enable registration config flag
|
||||||
|
|
@ -602,7 +594,8 @@ func handleRegistrationFlow(
|
||||||
valid, err := isValidMacLogin(cfg, r.Username, r.Password, r.Admin, r.Auth.Mac)
|
valid, err := isValidMacLogin(cfg, r.Username, r.Password, r.Admin, r.Auth.Mac)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("isValidMacLogin failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
} else if !valid {
|
} else if !valid {
|
||||||
return util.MessageResponse(http.StatusForbidden, "HMAC incorrect")
|
return util.MessageResponse(http.StatusForbidden, "HMAC incorrect")
|
||||||
}
|
}
|
||||||
|
|
@ -668,8 +661,8 @@ func handleApplicationServiceRegistration(
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
r registerRequest,
|
r registerRequest,
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
accountDB *accounts.Database,
|
accountDB accounts.Database,
|
||||||
deviceDB *devices.Database,
|
deviceDB devices.Database,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
// Check if we previously had issues extracting the access token from the
|
// Check if we previously had issues extracting the access token from the
|
||||||
// request.
|
// request.
|
||||||
|
|
@ -707,8 +700,8 @@ func checkAndCompleteFlow(
|
||||||
r registerRequest,
|
r registerRequest,
|
||||||
sessionID string,
|
sessionID string,
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
accountDB *accounts.Database,
|
accountDB accounts.Database,
|
||||||
deviceDB *devices.Database,
|
deviceDB devices.Database,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
if checkFlowCompleted(flow, cfg.Derived.Registration.Flows) {
|
if checkFlowCompleted(flow, cfg.Derived.Registration.Flows) {
|
||||||
// This flow was completed, registration can continue
|
// This flow was completed, registration can continue
|
||||||
|
|
@ -730,8 +723,8 @@ func checkAndCompleteFlow(
|
||||||
// LegacyRegister process register requests from the legacy v1 API
|
// LegacyRegister process register requests from the legacy v1 API
|
||||||
func LegacyRegister(
|
func LegacyRegister(
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
accountDB *accounts.Database,
|
accountDB accounts.Database,
|
||||||
deviceDB *devices.Database,
|
deviceDB devices.Database,
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
var r legacyRegisterRequest
|
var r legacyRegisterRequest
|
||||||
|
|
@ -758,7 +751,8 @@ func LegacyRegister(
|
||||||
|
|
||||||
valid, err := isValidMacLogin(cfg, r.Username, r.Password, r.Admin, r.Mac)
|
valid, err := isValidMacLogin(cfg, r.Username, r.Password, r.Admin, r.Mac)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("isValidMacLogin failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !valid {
|
if !valid {
|
||||||
|
|
@ -814,8 +808,8 @@ func parseAndValidateLegacyLogin(req *http.Request, r *legacyRegisterRequest) *u
|
||||||
// not all
|
// not all
|
||||||
func completeRegistration(
|
func completeRegistration(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
accountDB *accounts.Database,
|
accountDB accounts.Database,
|
||||||
deviceDB *devices.Database,
|
deviceDB devices.Database,
|
||||||
username, password, appserviceID string,
|
username, password, appserviceID string,
|
||||||
inhibitLogin common.WeakBoolean,
|
inhibitLogin common.WeakBoolean,
|
||||||
displayName, deviceID *string,
|
displayName, deviceID *string,
|
||||||
|
|
@ -991,8 +985,8 @@ type availableResponse struct {
|
||||||
// RegisterAvailable checks if the username is already taken or invalid.
|
// RegisterAvailable checks if the username is already taken or invalid.
|
||||||
func RegisterAvailable(
|
func RegisterAvailable(
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
cfg config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
accountDB *accounts.Database,
|
accountDB accounts.Database,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
username := req.URL.Query().Get("username")
|
username := req.URL.Query().Get("username")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ func newTag() gomatrix.TagContent {
|
||||||
// GetTags implements GET /_matrix/client/r0/user/{userID}/rooms/{roomID}/tags
|
// GetTags implements GET /_matrix/client/r0/user/{userID}/rooms/{roomID}/tags
|
||||||
func GetTags(
|
func GetTags(
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
accountDB *accounts.Database,
|
accountDB accounts.Database,
|
||||||
device *authtypes.Device,
|
device *authtypes.Device,
|
||||||
userID string,
|
userID string,
|
||||||
roomID string,
|
roomID string,
|
||||||
|
|
@ -56,7 +56,8 @@ func GetTags(
|
||||||
|
|
||||||
_, data, err := obtainSavedTags(req, userID, roomID, accountDB)
|
_, data, err := obtainSavedTags(req, userID, roomID, accountDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("obtainSavedTags failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if data == nil {
|
if data == nil {
|
||||||
|
|
@ -77,7 +78,7 @@ func GetTags(
|
||||||
// the tag to the "map" and saving the new "map" to the DB
|
// the tag to the "map" and saving the new "map" to the DB
|
||||||
func PutTag(
|
func PutTag(
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
accountDB *accounts.Database,
|
accountDB accounts.Database,
|
||||||
device *authtypes.Device,
|
device *authtypes.Device,
|
||||||
userID string,
|
userID string,
|
||||||
roomID string,
|
roomID string,
|
||||||
|
|
@ -99,20 +100,23 @@ func PutTag(
|
||||||
|
|
||||||
localpart, data, err := obtainSavedTags(req, userID, roomID, accountDB)
|
localpart, data, err := obtainSavedTags(req, userID, roomID, accountDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("obtainSavedTags failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
var tagContent gomatrix.TagContent
|
var tagContent gomatrix.TagContent
|
||||||
if data != nil {
|
if data != nil {
|
||||||
if err = json.Unmarshal(data.Content, &tagContent); err != nil {
|
if err = json.Unmarshal(data.Content, &tagContent); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("json.Unmarshal failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tagContent = newTag()
|
tagContent = newTag()
|
||||||
}
|
}
|
||||||
tagContent.Tags[tag] = properties
|
tagContent.Tags[tag] = properties
|
||||||
if err = saveTagData(req, localpart, roomID, accountDB, tagContent); err != nil {
|
if err = saveTagData(req, localpart, roomID, accountDB, tagContent); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("saveTagData failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send data to syncProducer in order to inform clients of changes
|
// Send data to syncProducer in order to inform clients of changes
|
||||||
|
|
@ -134,7 +138,7 @@ func PutTag(
|
||||||
// the "map" and then saving the new "map" in the DB
|
// the "map" and then saving the new "map" in the DB
|
||||||
func DeleteTag(
|
func DeleteTag(
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
accountDB *accounts.Database,
|
accountDB accounts.Database,
|
||||||
device *authtypes.Device,
|
device *authtypes.Device,
|
||||||
userID string,
|
userID string,
|
||||||
roomID string,
|
roomID string,
|
||||||
|
|
@ -151,7 +155,8 @@ func DeleteTag(
|
||||||
|
|
||||||
localpart, data, err := obtainSavedTags(req, userID, roomID, accountDB)
|
localpart, data, err := obtainSavedTags(req, userID, roomID, accountDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("obtainSavedTags failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are no tags in the database, exit
|
// If there are no tags in the database, exit
|
||||||
|
|
@ -166,7 +171,8 @@ func DeleteTag(
|
||||||
var tagContent gomatrix.TagContent
|
var tagContent gomatrix.TagContent
|
||||||
err = json.Unmarshal(data.Content, &tagContent)
|
err = json.Unmarshal(data.Content, &tagContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("json.Unmarshal failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether the tag to be deleted exists
|
// Check whether the tag to be deleted exists
|
||||||
|
|
@ -180,7 +186,8 @@ func DeleteTag(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err = saveTagData(req, localpart, roomID, accountDB, tagContent); err != nil {
|
if err = saveTagData(req, localpart, roomID, accountDB, tagContent); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("saveTagData failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send data to syncProducer in order to inform clients of changes
|
// Send data to syncProducer in order to inform clients of changes
|
||||||
|
|
@ -203,7 +210,7 @@ func obtainSavedTags(
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
userID string,
|
userID string,
|
||||||
roomID string,
|
roomID string,
|
||||||
accountDB *accounts.Database,
|
accountDB accounts.Database,
|
||||||
) (string, *gomatrixserverlib.ClientEvent, error) {
|
) (string, *gomatrixserverlib.ClientEvent, error) {
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -222,7 +229,7 @@ func saveTagData(
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
localpart string,
|
localpart string,
|
||||||
roomID string,
|
roomID string,
|
||||||
accountDB *accounts.Database,
|
accountDB accounts.Database,
|
||||||
Tag gomatrix.TagContent,
|
Tag gomatrix.TagContent,
|
||||||
) error {
|
) error {
|
||||||
newTagData, err := json.Marshal(Tag)
|
newTagData, err := json.Marshal(Tag)
|
||||||
|
|
|
||||||
|
|
@ -47,18 +47,18 @@ const pathPrefixUnstable = "/_matrix/client/unstable"
|
||||||
// applied:
|
// applied:
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
func Setup(
|
func Setup(
|
||||||
apiMux *mux.Router, cfg config.Dendrite,
|
apiMux *mux.Router, cfg *config.Dendrite,
|
||||||
producer *producers.RoomserverProducer,
|
producer *producers.RoomserverProducer,
|
||||||
queryAPI roomserverAPI.RoomserverQueryAPI,
|
queryAPI roomserverAPI.RoomserverQueryAPI,
|
||||||
aliasAPI roomserverAPI.RoomserverAliasAPI,
|
aliasAPI roomserverAPI.RoomserverAliasAPI,
|
||||||
asAPI appserviceAPI.AppServiceQueryAPI,
|
asAPI appserviceAPI.AppServiceQueryAPI,
|
||||||
accountDB *accounts.Database,
|
accountDB accounts.Database,
|
||||||
deviceDB *devices.Database,
|
deviceDB devices.Database,
|
||||||
federation *gomatrixserverlib.FederationClient,
|
federation *gomatrixserverlib.FederationClient,
|
||||||
keyRing gomatrixserverlib.KeyRing,
|
keyRing gomatrixserverlib.KeyRing,
|
||||||
userUpdateProducer *producers.UserUpdateProducer,
|
userUpdateProducer *producers.UserUpdateProducer,
|
||||||
syncProducer *producers.SyncAPIProducer,
|
syncProducer *producers.SyncAPIProducer,
|
||||||
typingProducer *producers.TypingServerProducer,
|
eduProducer *producers.EDUServerProducer,
|
||||||
transactionsCache *transactions.Cache,
|
transactionsCache *transactions.Cache,
|
||||||
federationSender federationSenderAPI.FederationSenderQueryAPI,
|
federationSender federationSenderAPI.FederationSenderQueryAPI,
|
||||||
) {
|
) {
|
||||||
|
|
@ -105,6 +105,12 @@ func Setup(
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
r0mux.Handle("/joined_rooms",
|
||||||
|
common.MakeAuthAPI("joined_rooms", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||||
|
return GetJoinedRooms(req, device, accountDB)
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
r0mux.Handle("/rooms/{roomID}/{membership:(?:join|kick|ban|unban|leave|invite)}",
|
r0mux.Handle("/rooms/{roomID}/{membership:(?:join|kick|ban|unban|leave|invite)}",
|
||||||
common.MakeAuthAPI("membership", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
common.MakeAuthAPI("membership", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||||
vars, err := common.URLDecodeMapValues(mux.Vars(req))
|
vars, err := common.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
@ -170,11 +176,11 @@ func Setup(
|
||||||
).Methods(http.MethodPut, http.MethodOptions)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
|
||||||
r0mux.Handle("/register", common.MakeExternalAPI("register", func(req *http.Request) util.JSONResponse {
|
r0mux.Handle("/register", common.MakeExternalAPI("register", func(req *http.Request) util.JSONResponse {
|
||||||
return Register(req, accountDB, deviceDB, &cfg)
|
return Register(req, accountDB, deviceDB, cfg)
|
||||||
})).Methods(http.MethodPost, http.MethodOptions)
|
})).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
|
||||||
v1mux.Handle("/register", common.MakeExternalAPI("register", func(req *http.Request) util.JSONResponse {
|
v1mux.Handle("/register", common.MakeExternalAPI("register", func(req *http.Request) util.JSONResponse {
|
||||||
return LegacyRegister(req, accountDB, deviceDB, &cfg)
|
return LegacyRegister(req, accountDB, deviceDB, cfg)
|
||||||
})).Methods(http.MethodPost, http.MethodOptions)
|
})).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
|
||||||
r0mux.Handle("/register/available", common.MakeExternalAPI("registerAvailable", func(req *http.Request) util.JSONResponse {
|
r0mux.Handle("/register/available", common.MakeExternalAPI("registerAvailable", func(req *http.Request) util.JSONResponse {
|
||||||
|
|
@ -187,7 +193,7 @@ func Setup(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.ErrorResponse(err)
|
return util.ErrorResponse(err)
|
||||||
}
|
}
|
||||||
return DirectoryRoom(req, vars["roomAlias"], federation, &cfg, aliasAPI, federationSender)
|
return DirectoryRoom(req, vars["roomAlias"], federation, cfg, aliasAPI, federationSender)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
|
|
@ -197,7 +203,7 @@ func Setup(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.ErrorResponse(err)
|
return util.ErrorResponse(err)
|
||||||
}
|
}
|
||||||
return SetLocalAlias(req, device, vars["roomAlias"], &cfg, aliasAPI)
|
return SetLocalAlias(req, device, vars["roomAlias"], cfg, aliasAPI)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPut, http.MethodOptions)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
|
||||||
|
|
@ -229,7 +235,7 @@ func Setup(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.ErrorResponse(err)
|
return util.ErrorResponse(err)
|
||||||
}
|
}
|
||||||
return SendTyping(req, device, vars["roomID"], vars["userID"], accountDB, typingProducer)
|
return SendTyping(req, device, vars["roomID"], vars["userID"], accountDB, eduProducer)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPut, http.MethodOptions)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
|
||||||
|
|
@ -301,7 +307,7 @@ func Setup(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.ErrorResponse(err)
|
return util.ErrorResponse(err)
|
||||||
}
|
}
|
||||||
return GetProfile(req, accountDB, &cfg, vars["userID"], asAPI, federation)
|
return GetProfile(req, accountDB, cfg, vars["userID"], asAPI, federation)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
|
|
@ -311,7 +317,7 @@ func Setup(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.ErrorResponse(err)
|
return util.ErrorResponse(err)
|
||||||
}
|
}
|
||||||
return GetAvatarURL(req, accountDB, &cfg, vars["userID"], asAPI, federation)
|
return GetAvatarURL(req, accountDB, cfg, vars["userID"], asAPI, federation)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
|
|
@ -321,7 +327,7 @@ func Setup(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.ErrorResponse(err)
|
return util.ErrorResponse(err)
|
||||||
}
|
}
|
||||||
return SetAvatarURL(req, accountDB, device, vars["userID"], userUpdateProducer, &cfg, producer, queryAPI)
|
return SetAvatarURL(req, accountDB, device, vars["userID"], userUpdateProducer, cfg, producer, queryAPI)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPut, http.MethodOptions)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
// Browsers use the OPTIONS HTTP method to check if the CORS policy allows
|
// Browsers use the OPTIONS HTTP method to check if the CORS policy allows
|
||||||
|
|
@ -333,7 +339,7 @@ func Setup(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.ErrorResponse(err)
|
return util.ErrorResponse(err)
|
||||||
}
|
}
|
||||||
return GetDisplayName(req, accountDB, &cfg, vars["userID"], asAPI, federation)
|
return GetDisplayName(req, accountDB, cfg, vars["userID"], asAPI, federation)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
|
|
@ -343,7 +349,7 @@ func Setup(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.ErrorResponse(err)
|
return util.ErrorResponse(err)
|
||||||
}
|
}
|
||||||
return SetDisplayName(req, accountDB, device, vars["userID"], userUpdateProducer, &cfg, producer, queryAPI)
|
return SetDisplayName(req, accountDB, device, vars["userID"], userUpdateProducer, cfg, producer, queryAPI)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPut, http.MethodOptions)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
// Browsers use the OPTIONS HTTP method to check if the CORS policy allows
|
// Browsers use the OPTIONS HTTP method to check if the CORS policy allows
|
||||||
|
|
@ -390,7 +396,7 @@ func Setup(
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
unstableMux.Handle("/thirdparty/protocols",
|
r0mux.Handle("/thirdparty/protocols",
|
||||||
common.MakeExternalAPI("thirdparty_protocols", func(req *http.Request) util.JSONResponse {
|
common.MakeExternalAPI("thirdparty_protocols", func(req *http.Request) util.JSONResponse {
|
||||||
// TODO: Return the third party protcols
|
// TODO: Return the third party protcols
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -503,6 +509,22 @@ func Setup(
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPut, http.MethodOptions)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
|
||||||
|
r0mux.Handle("/devices/{deviceID}",
|
||||||
|
common.MakeAuthAPI("delete_device", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||||
|
vars, err := common.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
if err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
return DeleteDeviceById(req, deviceDB, device, vars["deviceID"])
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodDelete, http.MethodOptions)
|
||||||
|
|
||||||
|
r0mux.Handle("/delete_devices",
|
||||||
|
common.MakeAuthAPI("delete_devices", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||||
|
return DeleteDevices(req, deviceDB, device)
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
|
||||||
// Stub implementations for sytest
|
// Stub implementations for sytest
|
||||||
r0mux.Handle("/events",
|
r0mux.Handle("/events",
|
||||||
common.MakeExternalAPI("events", func(req *http.Request) util.JSONResponse {
|
common.MakeExternalAPI("events", func(req *http.Request) util.JSONResponse {
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
|
// http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
|
||||||
|
|
@ -43,11 +44,20 @@ func SendEvent(
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
device *authtypes.Device,
|
device *authtypes.Device,
|
||||||
roomID, eventType string, txnID, stateKey *string,
|
roomID, eventType string, txnID, stateKey *string,
|
||||||
cfg config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
queryAPI api.RoomserverQueryAPI,
|
queryAPI api.RoomserverQueryAPI,
|
||||||
producer *producers.RoomserverProducer,
|
producer *producers.RoomserverProducer,
|
||||||
txnCache *transactions.Cache,
|
txnCache *transactions.Cache,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
|
verReq := api.QueryRoomVersionForRoomRequest{RoomID: roomID}
|
||||||
|
verRes := api.QueryRoomVersionForRoomResponse{}
|
||||||
|
if err := queryAPI.QueryRoomVersionForRoom(req.Context(), &verReq, &verRes); err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.UnsupportedRoomVersion(err.Error()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if txnID != nil {
|
if txnID != nil {
|
||||||
// Try to fetch response from transactionsCache
|
// Try to fetch response from transactionsCache
|
||||||
if res, ok := txnCache.FetchTransaction(device.AccessToken, *txnID); ok {
|
if res, ok := txnCache.FetchTransaction(device.AccessToken, *txnID); ok {
|
||||||
|
|
@ -71,11 +81,22 @@ func SendEvent(
|
||||||
// pass the new event to the roomserver and receive the correct event ID
|
// pass the new event to the roomserver and receive the correct event ID
|
||||||
// event ID in case of duplicate transaction is discarded
|
// event ID in case of duplicate transaction is discarded
|
||||||
eventID, err := producer.SendEvents(
|
eventID, err := producer.SendEvents(
|
||||||
req.Context(), []gomatrixserverlib.Event{*e}, cfg.Matrix.ServerName, txnAndSessionID,
|
req.Context(),
|
||||||
|
[]gomatrixserverlib.HeaderedEvent{
|
||||||
|
e.Headered(verRes.RoomVersion),
|
||||||
|
},
|
||||||
|
cfg.Matrix.ServerName,
|
||||||
|
txnAndSessionID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("producer.SendEvents failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
util.GetLogger(req.Context()).WithFields(logrus.Fields{
|
||||||
|
"event_id": eventID,
|
||||||
|
"room_id": roomID,
|
||||||
|
"room_version": verRes.RoomVersion,
|
||||||
|
}).Info("Sent event to roomserver")
|
||||||
|
|
||||||
res := util.JSONResponse{
|
res := util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
|
|
@ -93,7 +114,7 @@ func generateSendEvent(
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
device *authtypes.Device,
|
device *authtypes.Device,
|
||||||
roomID, eventType string, stateKey *string,
|
roomID, eventType string, stateKey *string,
|
||||||
cfg config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
queryAPI api.RoomserverQueryAPI,
|
queryAPI api.RoomserverQueryAPI,
|
||||||
) (*gomatrixserverlib.Event, *util.JSONResponse) {
|
) (*gomatrixserverlib.Event, *util.JSONResponse) {
|
||||||
// parse the incoming http request
|
// parse the incoming http request
|
||||||
|
|
@ -121,7 +142,8 @@ func generateSendEvent(
|
||||||
}
|
}
|
||||||
err = builder.SetContent(r)
|
err = builder.SetContent(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resErr := httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("builder.SetContent failed")
|
||||||
|
resErr := jsonerror.InternalServerError()
|
||||||
return nil, &resErr
|
return nil, &resErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,14 +155,15 @@ func generateSendEvent(
|
||||||
JSON: jsonerror.NotFound("Room does not exist"),
|
JSON: jsonerror.NotFound("Room does not exist"),
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
resErr := httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("common.BuildEvent failed")
|
||||||
|
resErr := jsonerror.InternalServerError()
|
||||||
return nil, &resErr
|
return nil, &resErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// check to see if this user can perform this operation
|
// check to see if this user can perform this operation
|
||||||
stateEvents := make([]*gomatrixserverlib.Event, len(queryRes.StateEvents))
|
stateEvents := make([]*gomatrixserverlib.Event, len(queryRes.StateEvents))
|
||||||
for i := range queryRes.StateEvents {
|
for i := range queryRes.StateEvents {
|
||||||
stateEvents[i] = &queryRes.StateEvents[i]
|
stateEvents[i] = &queryRes.StateEvents[i].Event
|
||||||
}
|
}
|
||||||
provider := gomatrixserverlib.NewAuthEvents(stateEvents)
|
provider := gomatrixserverlib.NewAuthEvents(stateEvents)
|
||||||
if err = gomatrixserverlib.Allowed(*e, &provider); err != nil {
|
if err = gomatrixserverlib.Allowed(*e, &provider); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@ type typingContentJSON struct {
|
||||||
// sends the typing events to client API typingProducer
|
// sends the typing events to client API typingProducer
|
||||||
func SendTyping(
|
func SendTyping(
|
||||||
req *http.Request, device *authtypes.Device, roomID string,
|
req *http.Request, device *authtypes.Device, roomID string,
|
||||||
userID string, accountDB *accounts.Database,
|
userID string, accountDB accounts.Database,
|
||||||
typingProducer *producers.TypingServerProducer,
|
eduProducer *producers.EDUServerProducer,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
if device.UserID != userID {
|
if device.UserID != userID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -46,7 +46,8 @@ func SendTyping(
|
||||||
|
|
||||||
localpart, err := userutil.ParseUsernameParam(userID, nil)
|
localpart, err := userutil.ParseUsernameParam(userID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("userutil.ParseUsernameParam failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that the user is a member of this room
|
// Verify that the user is a member of this room
|
||||||
|
|
@ -57,7 +58,8 @@ func SendTyping(
|
||||||
JSON: jsonerror.Forbidden("User not in this room"),
|
JSON: jsonerror.Forbidden("User not in this room"),
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("accountDB.GetMembershipInRoomByLocalPart failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse the incoming http request
|
// parse the incoming http request
|
||||||
|
|
@ -67,10 +69,11 @@ func SendTyping(
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = typingProducer.Send(
|
if err = eduProducer.SendTyping(
|
||||||
req.Context(), userID, roomID, r.Typing, r.Timeout,
|
req.Context(), userID, roomID, r.Typing, r.Timeout,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("eduProducer.Send failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ type threePIDsResponse struct {
|
||||||
// RequestEmailToken implements:
|
// RequestEmailToken implements:
|
||||||
// POST /account/3pid/email/requestToken
|
// POST /account/3pid/email/requestToken
|
||||||
// POST /register/email/requestToken
|
// POST /register/email/requestToken
|
||||||
func RequestEmailToken(req *http.Request, accountDB *accounts.Database, cfg config.Dendrite) util.JSONResponse {
|
func RequestEmailToken(req *http.Request, accountDB accounts.Database, cfg *config.Dendrite) util.JSONResponse {
|
||||||
var body threepid.EmailAssociationRequest
|
var body threepid.EmailAssociationRequest
|
||||||
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
|
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
|
||||||
return *reqErr
|
return *reqErr
|
||||||
|
|
@ -51,7 +51,8 @@ func RequestEmailToken(req *http.Request, accountDB *accounts.Database, cfg conf
|
||||||
// Check if the 3PID is already in use locally
|
// Check if the 3PID is already in use locally
|
||||||
localpart, err := accountDB.GetLocalpartForThreePID(req.Context(), body.Email, "email")
|
localpart, err := accountDB.GetLocalpartForThreePID(req.Context(), body.Email, "email")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("accountDB.GetLocalpartForThreePID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(localpart) > 0 {
|
if len(localpart) > 0 {
|
||||||
|
|
@ -71,7 +72,8 @@ func RequestEmailToken(req *http.Request, accountDB *accounts.Database, cfg conf
|
||||||
JSON: jsonerror.NotTrusted(body.IDServer),
|
JSON: jsonerror.NotTrusted(body.IDServer),
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("threepid.CreateSession failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -82,8 +84,8 @@ func RequestEmailToken(req *http.Request, accountDB *accounts.Database, cfg conf
|
||||||
|
|
||||||
// CheckAndSave3PIDAssociation implements POST /account/3pid
|
// CheckAndSave3PIDAssociation implements POST /account/3pid
|
||||||
func CheckAndSave3PIDAssociation(
|
func CheckAndSave3PIDAssociation(
|
||||||
req *http.Request, accountDB *accounts.Database, device *authtypes.Device,
|
req *http.Request, accountDB accounts.Database, device *authtypes.Device,
|
||||||
cfg config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
var body threepid.EmailAssociationCheckRequest
|
var body threepid.EmailAssociationCheckRequest
|
||||||
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
|
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
|
||||||
|
|
@ -98,7 +100,8 @@ func CheckAndSave3PIDAssociation(
|
||||||
JSON: jsonerror.NotTrusted(body.Creds.IDServer),
|
JSON: jsonerror.NotTrusted(body.Creds.IDServer),
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("threepid.CheckAssociation failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !verified {
|
if !verified {
|
||||||
|
|
@ -120,18 +123,21 @@ func CheckAndSave3PIDAssociation(
|
||||||
JSON: jsonerror.NotTrusted(body.Creds.IDServer),
|
JSON: jsonerror.NotTrusted(body.Creds.IDServer),
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("threepid.PublishAssociation failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the association in the database
|
// Save the association in the database
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = accountDB.SaveThreePIDAssociation(req.Context(), address, localpart, medium); err != nil {
|
if err = accountDB.SaveThreePIDAssociation(req.Context(), address, localpart, medium); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("accountsDB.SaveThreePIDAssociation failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -142,16 +148,18 @@ func CheckAndSave3PIDAssociation(
|
||||||
|
|
||||||
// GetAssociated3PIDs implements GET /account/3pid
|
// GetAssociated3PIDs implements GET /account/3pid
|
||||||
func GetAssociated3PIDs(
|
func GetAssociated3PIDs(
|
||||||
req *http.Request, accountDB *accounts.Database, device *authtypes.Device,
|
req *http.Request, accountDB accounts.Database, device *authtypes.Device,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
threepids, err := accountDB.GetThreePIDsForLocalpart(req.Context(), localpart)
|
threepids, err := accountDB.GetThreePIDsForLocalpart(req.Context(), localpart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("accountDB.GetThreePIDsForLocalpart failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -161,14 +169,15 @@ func GetAssociated3PIDs(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forget3PID implements POST /account/3pid/delete
|
// Forget3PID implements POST /account/3pid/delete
|
||||||
func Forget3PID(req *http.Request, accountDB *accounts.Database) util.JSONResponse {
|
func Forget3PID(req *http.Request, accountDB accounts.Database) util.JSONResponse {
|
||||||
var body authtypes.ThreePID
|
var body authtypes.ThreePID
|
||||||
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
|
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
|
||||||
return *reqErr
|
return *reqErr
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := accountDB.RemoveThreePIDAssociation(req.Context(), body.Address, body.Medium); err != nil {
|
if err := accountDB.RemoveThreePIDAssociation(req.Context(), body.Address, body.Medium); err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("accountDB.RemoveThreePIDAssociation failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/gomatrix"
|
"github.com/matrix-org/gomatrix"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
@ -31,7 +31,7 @@ import (
|
||||||
|
|
||||||
// RequestTurnServer implements:
|
// RequestTurnServer implements:
|
||||||
// GET /voip/turnServer
|
// GET /voip/turnServer
|
||||||
func RequestTurnServer(req *http.Request, device *authtypes.Device, cfg config.Dendrite) util.JSONResponse {
|
func RequestTurnServer(req *http.Request, device *authtypes.Device, cfg *config.Dendrite) util.JSONResponse {
|
||||||
turnConfig := cfg.TURN
|
turnConfig := cfg.TURN
|
||||||
|
|
||||||
// TODO Guest Support
|
// TODO Guest Support
|
||||||
|
|
@ -56,7 +56,8 @@ func RequestTurnServer(req *http.Request, device *authtypes.Device, cfg config.D
|
||||||
_, err := mac.Write([]byte(resp.Username))
|
_, err := mac.Write([]byte(resp.Username))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
util.GetLogger(req.Context()).WithError(err).Error("mac.Write failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Username = fmt.Sprintf("%d:%s", expiry, device.UserID)
|
resp.Username = fmt.Sprintf("%d:%s", expiry, device.UserID)
|
||||||
|
|
|
||||||
|
|
@ -86,8 +86,8 @@ var (
|
||||||
// can be emitted.
|
// can be emitted.
|
||||||
func CheckAndProcessInvite(
|
func CheckAndProcessInvite(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
device *authtypes.Device, body *MembershipRequest, cfg config.Dendrite,
|
device *authtypes.Device, body *MembershipRequest, cfg *config.Dendrite,
|
||||||
queryAPI api.RoomserverQueryAPI, db *accounts.Database,
|
queryAPI api.RoomserverQueryAPI, db accounts.Database,
|
||||||
producer *producers.RoomserverProducer, membership string, roomID string,
|
producer *producers.RoomserverProducer, membership string, roomID string,
|
||||||
evTime time.Time,
|
evTime time.Time,
|
||||||
) (inviteStoredOnIDServer bool, err error) {
|
) (inviteStoredOnIDServer bool, err error) {
|
||||||
|
|
@ -137,7 +137,7 @@ func CheckAndProcessInvite(
|
||||||
// Returns an error if a check or a request failed.
|
// Returns an error if a check or a request failed.
|
||||||
func queryIDServer(
|
func queryIDServer(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
db *accounts.Database, cfg config.Dendrite, device *authtypes.Device,
|
db accounts.Database, cfg *config.Dendrite, device *authtypes.Device,
|
||||||
body *MembershipRequest, roomID string,
|
body *MembershipRequest, roomID string,
|
||||||
) (lookupRes *idServerLookupResponse, storeInviteRes *idServerStoreInviteResponse, err error) {
|
) (lookupRes *idServerLookupResponse, storeInviteRes *idServerStoreInviteResponse, err error) {
|
||||||
if err = isTrusted(body.IDServer, cfg); err != nil {
|
if err = isTrusted(body.IDServer, cfg); err != nil {
|
||||||
|
|
@ -206,7 +206,7 @@ func queryIDServerLookup(ctx context.Context, body *MembershipRequest) (*idServe
|
||||||
// Returns an error if the request failed to send or if the response couldn't be parsed.
|
// Returns an error if the request failed to send or if the response couldn't be parsed.
|
||||||
func queryIDServerStoreInvite(
|
func queryIDServerStoreInvite(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
db *accounts.Database, cfg config.Dendrite, device *authtypes.Device,
|
db accounts.Database, cfg *config.Dendrite, device *authtypes.Device,
|
||||||
body *MembershipRequest, roomID string,
|
body *MembershipRequest, roomID string,
|
||||||
) (*idServerStoreInviteResponse, error) {
|
) (*idServerStoreInviteResponse, error) {
|
||||||
// Retrieve the sender's profile to get their display name
|
// Retrieve the sender's profile to get their display name
|
||||||
|
|
@ -330,7 +330,7 @@ func checkIDServerSignatures(
|
||||||
func emit3PIDInviteEvent(
|
func emit3PIDInviteEvent(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
body *MembershipRequest, res *idServerStoreInviteResponse,
|
body *MembershipRequest, res *idServerStoreInviteResponse,
|
||||||
device *authtypes.Device, roomID string, cfg config.Dendrite,
|
device *authtypes.Device, roomID string, cfg *config.Dendrite,
|
||||||
queryAPI api.RoomserverQueryAPI, producer *producers.RoomserverProducer,
|
queryAPI api.RoomserverQueryAPI, producer *producers.RoomserverProducer,
|
||||||
evTime time.Time,
|
evTime time.Time,
|
||||||
) error {
|
) error {
|
||||||
|
|
@ -353,12 +353,19 @@ func emit3PIDInviteEvent(
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var queryRes *api.QueryLatestEventsAndStateResponse
|
queryRes := api.QueryLatestEventsAndStateResponse{}
|
||||||
event, err := common.BuildEvent(ctx, builder, cfg, evTime, queryAPI, queryRes)
|
event, err := common.BuildEvent(ctx, builder, cfg, evTime, queryAPI, &queryRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = producer.SendEvents(ctx, []gomatrixserverlib.Event{*event}, cfg.Matrix.ServerName, nil)
|
_, err = producer.SendEvents(
|
||||||
|
ctx,
|
||||||
|
[]gomatrixserverlib.HeaderedEvent{
|
||||||
|
(*event).Headered(queryRes.RoomVersion),
|
||||||
|
},
|
||||||
|
cfg.Matrix.ServerName,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ type Credentials struct {
|
||||||
// Returns an error if there was a problem sending the request or decoding the
|
// Returns an error if there was a problem sending the request or decoding the
|
||||||
// response, or if the identity server responded with a non-OK status.
|
// response, or if the identity server responded with a non-OK status.
|
||||||
func CreateSession(
|
func CreateSession(
|
||||||
ctx context.Context, req EmailAssociationRequest, cfg config.Dendrite,
|
ctx context.Context, req EmailAssociationRequest, cfg *config.Dendrite,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
if err := isTrusted(req.IDServer, cfg); err != nil {
|
if err := isTrusted(req.IDServer, cfg); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
@ -101,7 +101,7 @@ func CreateSession(
|
||||||
// Returns an error if there was a problem sending the request or decoding the
|
// Returns an error if there was a problem sending the request or decoding the
|
||||||
// response, or if the identity server responded with a non-OK status.
|
// response, or if the identity server responded with a non-OK status.
|
||||||
func CheckAssociation(
|
func CheckAssociation(
|
||||||
ctx context.Context, creds Credentials, cfg config.Dendrite,
|
ctx context.Context, creds Credentials, cfg *config.Dendrite,
|
||||||
) (bool, string, string, error) {
|
) (bool, string, string, error) {
|
||||||
if err := isTrusted(creds.IDServer, cfg); err != nil {
|
if err := isTrusted(creds.IDServer, cfg); err != nil {
|
||||||
return false, "", "", err
|
return false, "", "", err
|
||||||
|
|
@ -142,7 +142,7 @@ func CheckAssociation(
|
||||||
// identifier and a Matrix ID.
|
// identifier and a Matrix ID.
|
||||||
// Returns an error if there was a problem sending the request or decoding the
|
// Returns an error if there was a problem sending the request or decoding the
|
||||||
// response, or if the identity server responded with a non-OK status.
|
// response, or if the identity server responded with a non-OK status.
|
||||||
func PublishAssociation(creds Credentials, userID string, cfg config.Dendrite) error {
|
func PublishAssociation(creds Credentials, userID string, cfg *config.Dendrite) error {
|
||||||
if err := isTrusted(creds.IDServer, cfg); err != nil {
|
if err := isTrusted(creds.IDServer, cfg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -177,7 +177,7 @@ func PublishAssociation(creds Credentials, userID string, cfg config.Dendrite) e
|
||||||
// isTrusted checks if a given identity server is part of the list of trusted
|
// isTrusted checks if a given identity server is part of the list of trusted
|
||||||
// identity servers in the configuration file.
|
// identity servers in the configuration file.
|
||||||
// Returns an error if the server isn't trusted.
|
// Returns an error if the server isn't trusted.
|
||||||
func isTrusted(idServer string, cfg config.Dendrite) error {
|
func isTrusted(idServer string, cfg *config.Dendrite) error {
|
||||||
for _, server := range cfg.Matrix.TrustedIDServers {
|
for _, server := range cfg.Matrix.TrustedIDServers {
|
||||||
if idServer == server {
|
if idServer == server {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -103,12 +103,14 @@ func main() {
|
||||||
// Build an event and write the event to the output.
|
// Build an event and write the event to the output.
|
||||||
func buildAndOutput() gomatrixserverlib.EventReference {
|
func buildAndOutput() gomatrixserverlib.EventReference {
|
||||||
eventID++
|
eventID++
|
||||||
id := fmt.Sprintf("$%d:%s", eventID, *serverName)
|
|
||||||
now = time.Unix(0, 0)
|
now = time.Unix(0, 0)
|
||||||
name := gomatrixserverlib.ServerName(*serverName)
|
name := gomatrixserverlib.ServerName(*serverName)
|
||||||
key := gomatrixserverlib.KeyID(*keyID)
|
key := gomatrixserverlib.KeyID(*keyID)
|
||||||
|
|
||||||
event, err := b.Build(id, now, name, key, privateKey)
|
event, err := b.Build(
|
||||||
|
now, name, key, privateKey,
|
||||||
|
gomatrixserverlib.RoomVersionV1,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -125,9 +127,9 @@ func writeEvent(event gomatrixserverlib.Event) {
|
||||||
if *format == "InputRoomEvent" {
|
if *format == "InputRoomEvent" {
|
||||||
var ire api.InputRoomEvent
|
var ire api.InputRoomEvent
|
||||||
ire.Kind = api.KindNew
|
ire.Kind = api.KindNew
|
||||||
ire.Event = event
|
ire.Event = event.Headered(gomatrixserverlib.RoomVersionV1)
|
||||||
authEventIDs := []string{}
|
authEventIDs := []string{}
|
||||||
for _, ref := range b.AuthEvents {
|
for _, ref := range b.AuthEvents.([]gomatrixserverlib.EventReference) {
|
||||||
authEventIDs = append(authEventIDs, ref.EventID)
|
authEventIDs = append(authEventIDs, ref.EventID)
|
||||||
}
|
}
|
||||||
ire.AuthEventIDs = authEventIDs
|
ire.AuthEventIDs = authEventIDs
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ import (
|
||||||
"github.com/matrix-org/dendrite/common/basecomponent"
|
"github.com/matrix-org/dendrite/common/basecomponent"
|
||||||
"github.com/matrix-org/dendrite/common/keydb"
|
"github.com/matrix-org/dendrite/common/keydb"
|
||||||
"github.com/matrix-org/dendrite/common/transactions"
|
"github.com/matrix-org/dendrite/common/transactions"
|
||||||
"github.com/matrix-org/dendrite/typingserver"
|
"github.com/matrix-org/dendrite/eduserver"
|
||||||
"github.com/matrix-org/dendrite/typingserver/cache"
|
"github.com/matrix-org/dendrite/eduserver/cache"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -38,11 +38,11 @@ func main() {
|
||||||
asQuery := base.CreateHTTPAppServiceAPIs()
|
asQuery := base.CreateHTTPAppServiceAPIs()
|
||||||
alias, input, query := base.CreateHTTPRoomserverAPIs()
|
alias, input, query := base.CreateHTTPRoomserverAPIs()
|
||||||
fedSenderAPI := base.CreateHTTPFederationSenderAPIs()
|
fedSenderAPI := base.CreateHTTPFederationSenderAPIs()
|
||||||
typingInputAPI := typingserver.SetupTypingServerComponent(base, cache.NewTypingCache())
|
eduInputAPI := eduserver.SetupEDUServerComponent(base, cache.New())
|
||||||
|
|
||||||
clientapi.SetupClientAPIComponent(
|
clientapi.SetupClientAPIComponent(
|
||||||
base, deviceDB, accountDB, federation, &keyRing,
|
base, deviceDB, accountDB, federation, &keyRing,
|
||||||
alias, input, query, typingInputAPI, asQuery, transactions.New(), fedSenderAPI,
|
alias, input, query, eduInputAPI, asQuery, transactions.New(), fedSenderAPI,
|
||||||
)
|
)
|
||||||
|
|
||||||
base.SetupAndServeHTTP(string(base.Cfg.Bind.ClientAPI), string(base.Cfg.Listen.ClientAPI))
|
base.SetupAndServeHTTP(string(base.Cfg.Bind.ClientAPI), string(base.Cfg.Listen.ClientAPI))
|
||||||
|
|
|
||||||
|
|
@ -16,22 +16,22 @@ import (
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/common/basecomponent"
|
"github.com/matrix-org/dendrite/common/basecomponent"
|
||||||
"github.com/matrix-org/dendrite/typingserver"
|
"github.com/matrix-org/dendrite/eduserver"
|
||||||
"github.com/matrix-org/dendrite/typingserver/cache"
|
"github.com/matrix-org/dendrite/eduserver/cache"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cfg := basecomponent.ParseFlags()
|
cfg := basecomponent.ParseFlags()
|
||||||
base := basecomponent.NewBaseDendrite(cfg, "TypingServerAPI")
|
base := basecomponent.NewBaseDendrite(cfg, "EDUServerAPI")
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := base.Close(); err != nil {
|
if err := base.Close(); err != nil {
|
||||||
logrus.WithError(err).Warn("BaseDendrite close failed")
|
logrus.WithError(err).Warn("BaseDendrite close failed")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
typingserver.SetupTypingServerComponent(base, cache.NewTypingCache())
|
eduserver.SetupEDUServerComponent(base, cache.New())
|
||||||
|
|
||||||
base.SetupAndServeHTTP(string(base.Cfg.Bind.TypingServer), string(base.Cfg.Listen.TypingServer))
|
base.SetupAndServeHTTP(string(base.Cfg.Bind.EDUServer), string(base.Cfg.Listen.EDUServer))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -15,8 +15,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
"github.com/matrix-org/dendrite/common/basecomponent"
|
"github.com/matrix-org/dendrite/common/basecomponent"
|
||||||
"github.com/matrix-org/dendrite/common/keydb"
|
"github.com/matrix-org/dendrite/common/keydb"
|
||||||
|
"github.com/matrix-org/dendrite/eduserver"
|
||||||
|
"github.com/matrix-org/dendrite/eduserver/cache"
|
||||||
"github.com/matrix-org/dendrite/federationapi"
|
"github.com/matrix-org/dendrite/federationapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -34,10 +37,12 @@ func main() {
|
||||||
|
|
||||||
alias, input, query := base.CreateHTTPRoomserverAPIs()
|
alias, input, query := base.CreateHTTPRoomserverAPIs()
|
||||||
asQuery := base.CreateHTTPAppServiceAPIs()
|
asQuery := base.CreateHTTPAppServiceAPIs()
|
||||||
|
eduInputAPI := eduserver.SetupEDUServerComponent(base, cache.New())
|
||||||
|
eduProducer := producers.NewEDUServerProducer(eduInputAPI)
|
||||||
|
|
||||||
federationapi.SetupFederationAPIComponent(
|
federationapi.SetupFederationAPIComponent(
|
||||||
base, accountDB, deviceDB, federation, &keyRing,
|
base, accountDB, deviceDB, federation, &keyRing,
|
||||||
alias, input, query, asQuery, federationSender,
|
alias, input, query, asQuery, federationSender, eduProducer,
|
||||||
)
|
)
|
||||||
|
|
||||||
base.SetupAndServeHTTP(string(base.Cfg.Bind.FederationAPI), string(base.Cfg.Listen.FederationAPI))
|
base.SetupAndServeHTTP(string(base.Cfg.Bind.FederationAPI), string(base.Cfg.Listen.FederationAPI))
|
||||||
|
|
|
||||||
|
|
@ -20,18 +20,19 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/appservice"
|
"github.com/matrix-org/dendrite/appservice"
|
||||||
"github.com/matrix-org/dendrite/clientapi"
|
"github.com/matrix-org/dendrite/clientapi"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/common/basecomponent"
|
"github.com/matrix-org/dendrite/common/basecomponent"
|
||||||
"github.com/matrix-org/dendrite/common/keydb"
|
"github.com/matrix-org/dendrite/common/keydb"
|
||||||
"github.com/matrix-org/dendrite/common/transactions"
|
"github.com/matrix-org/dendrite/common/transactions"
|
||||||
|
"github.com/matrix-org/dendrite/eduserver"
|
||||||
|
"github.com/matrix-org/dendrite/eduserver/cache"
|
||||||
"github.com/matrix-org/dendrite/federationapi"
|
"github.com/matrix-org/dendrite/federationapi"
|
||||||
"github.com/matrix-org/dendrite/federationsender"
|
"github.com/matrix-org/dendrite/federationsender"
|
||||||
"github.com/matrix-org/dendrite/mediaapi"
|
"github.com/matrix-org/dendrite/mediaapi"
|
||||||
"github.com/matrix-org/dendrite/publicroomsapi"
|
"github.com/matrix-org/dendrite/publicroomsapi"
|
||||||
"github.com/matrix-org/dendrite/roomserver"
|
"github.com/matrix-org/dendrite/roomserver"
|
||||||
"github.com/matrix-org/dendrite/syncapi"
|
"github.com/matrix-org/dendrite/syncapi"
|
||||||
"github.com/matrix-org/dendrite/typingserver"
|
|
||||||
"github.com/matrix-org/dendrite/typingserver/cache"
|
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
@ -56,7 +57,7 @@ func main() {
|
||||||
keyRing := keydb.CreateKeyRing(federation.Client, keyDB)
|
keyRing := keydb.CreateKeyRing(federation.Client, keyDB)
|
||||||
|
|
||||||
alias, input, query := roomserver.SetupRoomServerComponent(base)
|
alias, input, query := roomserver.SetupRoomServerComponent(base)
|
||||||
typingInputAPI := typingserver.SetupTypingServerComponent(base, cache.NewTypingCache())
|
eduInputAPI := eduserver.SetupEDUServerComponent(base, cache.New())
|
||||||
asQuery := appservice.SetupAppServiceAPIComponent(
|
asQuery := appservice.SetupAppServiceAPIComponent(
|
||||||
base, accountDB, deviceDB, federation, alias, query, transactions.New(),
|
base, accountDB, deviceDB, federation, alias, query, transactions.New(),
|
||||||
)
|
)
|
||||||
|
|
@ -65,11 +66,12 @@ func main() {
|
||||||
clientapi.SetupClientAPIComponent(
|
clientapi.SetupClientAPIComponent(
|
||||||
base, deviceDB, accountDB,
|
base, deviceDB, accountDB,
|
||||||
federation, &keyRing, alias, input, query,
|
federation, &keyRing, alias, input, query,
|
||||||
typingInputAPI, asQuery, transactions.New(), fedSenderAPI,
|
eduInputAPI, asQuery, transactions.New(), fedSenderAPI,
|
||||||
)
|
)
|
||||||
federationapi.SetupFederationAPIComponent(base, accountDB, deviceDB, federation, &keyRing, alias, input, query, asQuery, fedSenderAPI)
|
eduProducer := producers.NewEDUServerProducer(eduInputAPI)
|
||||||
|
federationapi.SetupFederationAPIComponent(base, accountDB, deviceDB, federation, &keyRing, alias, input, query, asQuery, fedSenderAPI, eduProducer)
|
||||||
mediaapi.SetupMediaAPIComponent(base, deviceDB)
|
mediaapi.SetupMediaAPIComponent(base, deviceDB)
|
||||||
publicroomsapi.SetupPublicRoomsAPIComponent(base, deviceDB, query)
|
publicroomsapi.SetupPublicRoomsAPIComponent(base, deviceDB, query, federation, nil)
|
||||||
syncapi.SetupSyncAPIComponent(base, deviceDB, accountDB, query, federation, cfg)
|
syncapi.SetupSyncAPIComponent(base, deviceDB, accountDB, query, federation, cfg)
|
||||||
|
|
||||||
httpHandler := common.WrapHandlerInCORS(base.APIMux)
|
httpHandler := common.WrapHandlerInCORS(base.APIMux)
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ func main() {
|
||||||
|
|
||||||
_, _, query := base.CreateHTTPRoomserverAPIs()
|
_, _, query := base.CreateHTTPRoomserverAPIs()
|
||||||
|
|
||||||
publicroomsapi.SetupPublicRoomsAPIComponent(base, deviceDB, query)
|
publicroomsapi.SetupPublicRoomsAPIComponent(base, deviceDB, query, nil, nil)
|
||||||
|
|
||||||
base.SetupAndServeHTTP(string(base.Cfg.Bind.PublicRoomsAPI), string(base.Cfg.Listen.PublicRoomsAPI))
|
base.SetupAndServeHTTP(string(base.Cfg.Bind.PublicRoomsAPI), string(base.Cfg.Listen.PublicRoomsAPI))
|
||||||
|
|
||||||
|
|
|
||||||
104
cmd/dendritejs/jsServer.go
Normal file
104
cmd/dendritejs/jsServer.go
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build wasm
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"syscall/js"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSServer exposes an HTTP-like server interface which allows JS to 'send' requests to it.
|
||||||
|
type JSServer struct {
|
||||||
|
// The router which will service requests
|
||||||
|
Mux *http.ServeMux
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnRequestFromJS is the function that JS will invoke when there is a new request.
|
||||||
|
// The JS function signature is:
|
||||||
|
// function(reqString: string): Promise<{result: string, error: string}>
|
||||||
|
// Usage is like:
|
||||||
|
// const res = await global._go_js_server.fetch(reqString);
|
||||||
|
// if (res.error) {
|
||||||
|
// // handle error: this is a 'network' error, not a non-2xx error.
|
||||||
|
// }
|
||||||
|
// const rawHttpResponse = res.result;
|
||||||
|
func (h *JSServer) OnRequestFromJS(this js.Value, args []js.Value) interface{} {
|
||||||
|
// we HAVE to spawn a new goroutine and return immediately or else Go will deadlock
|
||||||
|
// if this request blocks at all e.g for /sync calls
|
||||||
|
httpStr := args[0].String()
|
||||||
|
promise := js.Global().Get("Promise").New(js.FuncOf(func(pthis js.Value, pargs []js.Value) interface{} {
|
||||||
|
// The initial callback code for new Promise() is also called on the critical path, which is why
|
||||||
|
// we need to put this in an immediately invoked goroutine.
|
||||||
|
go func() {
|
||||||
|
resolve := pargs[0]
|
||||||
|
fmt.Println("Received request:")
|
||||||
|
fmt.Printf("%s\n", httpStr)
|
||||||
|
resStr, err := h.handle(httpStr)
|
||||||
|
errStr := ""
|
||||||
|
if err != nil {
|
||||||
|
errStr = err.Error()
|
||||||
|
}
|
||||||
|
fmt.Println("Sending response:")
|
||||||
|
fmt.Printf("%s\n", resStr)
|
||||||
|
resolve.Invoke(map[string]interface{}{
|
||||||
|
"result": resStr,
|
||||||
|
"error": errStr,
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
return promise
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle invokes the http.ServeMux for this request and returns the raw HTTP response.
|
||||||
|
func (h *JSServer) handle(httpStr string) (resStr string, err error) {
|
||||||
|
req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(httpStr)))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
h.Mux.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
res := w.Result()
|
||||||
|
var resBuffer strings.Builder
|
||||||
|
err = res.Write(&resBuffer)
|
||||||
|
return resBuffer.String(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServe registers a variable in JS-land with the given namespace. This variable is
|
||||||
|
// a function which JS-land can call to 'send' HTTP requests. The function is attached to
|
||||||
|
// a global object called "_go_js_server". See OnRequestFromJS for more info.
|
||||||
|
func (h *JSServer) ListenAndServe(namespace string) {
|
||||||
|
globalName := "_go_js_server"
|
||||||
|
// register a hook in JS-land for it to invoke stuff
|
||||||
|
server := js.Global().Get(globalName)
|
||||||
|
if !server.Truthy() {
|
||||||
|
server = js.Global().Get("Object").New()
|
||||||
|
js.Global().Set(globalName, server)
|
||||||
|
}
|
||||||
|
|
||||||
|
server.Set(namespace, js.FuncOf(h.OnRequestFromJS))
|
||||||
|
|
||||||
|
fmt.Printf("Listening for requests from JS on function %s.%s\n", globalName, namespace)
|
||||||
|
// Block forever to mimic http.ListenAndServe
|
||||||
|
select {}
|
||||||
|
}
|
||||||
84
cmd/dendritejs/keyfetcher.go
Normal file
84
cmd/dendritejs/keyfetcher.go
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build wasm
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/libp2p/go-libp2p-core/peer"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const libp2pMatrixKeyID = "ed25519:libp2p-dendrite"
|
||||||
|
|
||||||
|
type libp2pKeyFetcher struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchKeys looks up a batch of public keys.
|
||||||
|
// Takes a map from (server name, key ID) pairs to timestamp.
|
||||||
|
// The timestamp is when the keys need to be vaild up to.
|
||||||
|
// Returns a map from (server name, key ID) pairs to server key objects for
|
||||||
|
// that server name containing that key ID
|
||||||
|
// The result may have fewer (server name, key ID) pairs than were in the request.
|
||||||
|
// The result may have more (server name, key ID) pairs than were in the request.
|
||||||
|
// Returns an error if there was a problem fetching the keys.
|
||||||
|
func (f *libp2pKeyFetcher) FetchKeys(
|
||||||
|
ctx context.Context,
|
||||||
|
requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp,
|
||||||
|
) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) {
|
||||||
|
res := make(map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult)
|
||||||
|
for req := range requests {
|
||||||
|
if req.KeyID != libp2pMatrixKeyID {
|
||||||
|
return nil, fmt.Errorf("FetchKeys: cannot fetch key with ID %s, should be %s", req.KeyID, libp2pMatrixKeyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The server name is a libp2p peer ID
|
||||||
|
peerIDStr := string(req.ServerName)
|
||||||
|
peerID, err := peer.Decode(peerIDStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to decode peer ID from server name '%s': %w", peerIDStr, err)
|
||||||
|
}
|
||||||
|
pubKey, err := peerID.ExtractPublicKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to extract public key from peer ID: %w", err)
|
||||||
|
}
|
||||||
|
pubKeyBytes, err := pubKey.Raw()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to extract raw bytes from public key: %w", err)
|
||||||
|
}
|
||||||
|
util.GetLogger(ctx).Info("libp2pKeyFetcher.FetchKeys: Using public key %v for server name %s", pubKeyBytes, req.ServerName)
|
||||||
|
|
||||||
|
b64Key := gomatrixserverlib.Base64String(pubKeyBytes)
|
||||||
|
res[req] = gomatrixserverlib.PublicKeyLookupResult{
|
||||||
|
VerifyKey: gomatrixserverlib.VerifyKey{
|
||||||
|
Key: b64Key,
|
||||||
|
},
|
||||||
|
ExpiredTS: gomatrixserverlib.PublicKeyNotExpired,
|
||||||
|
ValidUntilTS: gomatrixserverlib.AsTimestamp(time.Now().Add(24 * time.Hour * 365)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetcherName returns the name of this fetcher, which can then be used for
|
||||||
|
// logging errors etc.
|
||||||
|
func (f *libp2pKeyFetcher) FetcherName() string {
|
||||||
|
return "libp2pKeyFetcher"
|
||||||
|
}
|
||||||
169
cmd/dendritejs/main.go
Normal file
169
cmd/dendritejs/main.go
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build wasm
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/appservice"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/dendrite/common/basecomponent"
|
||||||
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
|
"github.com/matrix-org/dendrite/common/transactions"
|
||||||
|
"github.com/matrix-org/dendrite/eduserver"
|
||||||
|
"github.com/matrix-org/dendrite/eduserver/cache"
|
||||||
|
"github.com/matrix-org/dendrite/federationapi"
|
||||||
|
"github.com/matrix-org/dendrite/federationsender"
|
||||||
|
"github.com/matrix-org/dendrite/mediaapi"
|
||||||
|
"github.com/matrix-org/dendrite/publicroomsapi"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver"
|
||||||
|
"github.com/matrix-org/dendrite/syncapi"
|
||||||
|
"github.com/matrix-org/go-http-js-libp2p/go_http_js_libp2p"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
_ "github.com/matrix-org/go-sqlite3-js"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
fmt.Println("dendrite.js starting...")
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateKey() ed25519.PrivateKey {
|
||||||
|
_, priv, err := ed25519.GenerateKey(nil)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatalf("Failed to generate ed25519 key: %s", err)
|
||||||
|
}
|
||||||
|
return priv
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFederationClient(cfg *config.Dendrite, node *go_http_js_libp2p.P2pLocalNode) *gomatrixserverlib.FederationClient {
|
||||||
|
fmt.Println("Running in js-libp2p federation mode")
|
||||||
|
fmt.Println("Warning: Federation with non-libp2p homeservers will not work in this mode yet!")
|
||||||
|
tr := go_http_js_libp2p.NewP2pTransport(node)
|
||||||
|
|
||||||
|
fed := gomatrixserverlib.NewFederationClient(
|
||||||
|
cfg.Matrix.ServerName, cfg.Matrix.KeyID, cfg.Matrix.PrivateKey,
|
||||||
|
)
|
||||||
|
fed.Client = *gomatrixserverlib.NewClientWithTransport(tr)
|
||||||
|
|
||||||
|
return fed
|
||||||
|
}
|
||||||
|
|
||||||
|
func createP2PNode(privKey ed25519.PrivateKey) (serverName string, node *go_http_js_libp2p.P2pLocalNode) {
|
||||||
|
hosted := "/dns4/rendezvous.matrix.org/tcp/8443/wss/p2p-websocket-star/"
|
||||||
|
node = go_http_js_libp2p.NewP2pLocalNode("org.matrix.p2p.experiment", privKey.Seed(), []string{hosted})
|
||||||
|
serverName = node.Id
|
||||||
|
fmt.Println("p2p assigned ServerName: ", serverName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg := &config.Dendrite{}
|
||||||
|
cfg.SetDefaults()
|
||||||
|
cfg.Kafka.UseNaffka = true
|
||||||
|
cfg.Database.Account = "file:dendritejs_account.db"
|
||||||
|
cfg.Database.AppService = "file:dendritejs_appservice.db"
|
||||||
|
cfg.Database.Device = "file:dendritejs_device.db"
|
||||||
|
cfg.Database.FederationSender = "file:dendritejs_fedsender.db"
|
||||||
|
cfg.Database.MediaAPI = "file:dendritejs_mediaapi.db"
|
||||||
|
cfg.Database.Naffka = "file:dendritejs_naffka.db"
|
||||||
|
cfg.Database.PublicRoomsAPI = "file:dendritejs_publicrooms.db"
|
||||||
|
cfg.Database.RoomServer = "file:dendritejs_roomserver.db"
|
||||||
|
cfg.Database.ServerKey = "file:dendritejs_serverkey.db"
|
||||||
|
cfg.Database.SyncAPI = "file:dendritejs_syncapi.db"
|
||||||
|
cfg.Kafka.Topics.UserUpdates = "user_updates"
|
||||||
|
cfg.Kafka.Topics.OutputTypingEvent = "output_typing_event"
|
||||||
|
cfg.Kafka.Topics.OutputClientData = "output_client_data"
|
||||||
|
cfg.Kafka.Topics.OutputRoomEvent = "output_room_event"
|
||||||
|
cfg.Matrix.TrustedIDServers = []string{
|
||||||
|
"matrix.org", "vector.im",
|
||||||
|
}
|
||||||
|
cfg.Matrix.KeyID = libp2pMatrixKeyID
|
||||||
|
cfg.Matrix.PrivateKey = generateKey()
|
||||||
|
|
||||||
|
serverName, node := createP2PNode(cfg.Matrix.PrivateKey)
|
||||||
|
cfg.Matrix.ServerName = gomatrixserverlib.ServerName(serverName)
|
||||||
|
|
||||||
|
if err := cfg.Derive(); err != nil {
|
||||||
|
logrus.Fatalf("Failed to derive values from config: %s", err)
|
||||||
|
}
|
||||||
|
base := basecomponent.NewBaseDendrite(cfg, "Monolith")
|
||||||
|
defer base.Close() // nolint: errcheck
|
||||||
|
|
||||||
|
accountDB := base.CreateAccountsDB()
|
||||||
|
deviceDB := base.CreateDeviceDB()
|
||||||
|
keyDB := base.CreateKeyDB()
|
||||||
|
federation := createFederationClient(cfg, node)
|
||||||
|
keyRing := gomatrixserverlib.KeyRing{
|
||||||
|
KeyFetchers: []gomatrixserverlib.KeyFetcher{
|
||||||
|
&libp2pKeyFetcher{},
|
||||||
|
},
|
||||||
|
KeyDatabase: keyDB,
|
||||||
|
}
|
||||||
|
p2pPublicRoomProvider := NewLibP2PPublicRoomsProvider(node)
|
||||||
|
|
||||||
|
alias, input, query := roomserver.SetupRoomServerComponent(base)
|
||||||
|
eduInputAPI := eduserver.SetupEDUServerComponent(base, cache.New())
|
||||||
|
asQuery := appservice.SetupAppServiceAPIComponent(
|
||||||
|
base, accountDB, deviceDB, federation, alias, query, transactions.New(),
|
||||||
|
)
|
||||||
|
fedSenderAPI := federationsender.SetupFederationSenderComponent(base, federation, query)
|
||||||
|
|
||||||
|
clientapi.SetupClientAPIComponent(
|
||||||
|
base, deviceDB, accountDB,
|
||||||
|
federation, &keyRing, alias, input, query,
|
||||||
|
eduInputAPI, asQuery, transactions.New(), fedSenderAPI,
|
||||||
|
)
|
||||||
|
eduProducer := producers.NewEDUServerProducer(eduInputAPI)
|
||||||
|
federationapi.SetupFederationAPIComponent(base, accountDB, deviceDB, federation, &keyRing, alias, input, query, asQuery, fedSenderAPI, eduProducer)
|
||||||
|
mediaapi.SetupMediaAPIComponent(base, deviceDB)
|
||||||
|
publicroomsapi.SetupPublicRoomsAPIComponent(base, deviceDB, query, federation, p2pPublicRoomProvider)
|
||||||
|
syncapi.SetupSyncAPIComponent(base, deviceDB, accountDB, query, federation, cfg)
|
||||||
|
|
||||||
|
httpHandler := common.WrapHandlerInCORS(base.APIMux)
|
||||||
|
|
||||||
|
http.Handle("/", httpHandler)
|
||||||
|
|
||||||
|
// Expose the matrix APIs via libp2p-js - for federation traffic
|
||||||
|
if node != nil {
|
||||||
|
go func() {
|
||||||
|
logrus.Info("Listening on libp2p-js host ID ", node.Id)
|
||||||
|
s := JSServer{
|
||||||
|
Mux: http.DefaultServeMux,
|
||||||
|
}
|
||||||
|
s.ListenAndServe("p2p")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose the matrix APIs via fetch - for local traffic
|
||||||
|
go func() {
|
||||||
|
logrus.Info("Listening for service-worker fetch traffic")
|
||||||
|
s := JSServer{
|
||||||
|
Mux: http.DefaultServeMux,
|
||||||
|
}
|
||||||
|
s.ListenAndServe("fetch")
|
||||||
|
}()
|
||||||
|
|
||||||
|
// We want to block forever to let the fetch and libp2p handler serve the APIs
|
||||||
|
select {}
|
||||||
|
}
|
||||||
23
cmd/dendritejs/main_noop.go
Normal file
23
cmd/dendritejs/main_noop.go
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build !wasm
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("dendritejs: no-op when not compiling for WebAssembly")
|
||||||
|
}
|
||||||
46
cmd/dendritejs/publicrooms.go
Normal file
46
cmd/dendritejs/publicrooms.go
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build wasm
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/matrix-org/go-http-js-libp2p/go_http_js_libp2p"
|
||||||
|
)
|
||||||
|
|
||||||
|
type libp2pPublicRoomsProvider struct {
|
||||||
|
node *go_http_js_libp2p.P2pLocalNode
|
||||||
|
providers []go_http_js_libp2p.PeerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLibP2PPublicRoomsProvider(node *go_http_js_libp2p.P2pLocalNode) *libp2pPublicRoomsProvider {
|
||||||
|
p := &libp2pPublicRoomsProvider{
|
||||||
|
node: node,
|
||||||
|
}
|
||||||
|
node.RegisterFoundProviders(p.foundProviders)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *libp2pPublicRoomsProvider) foundProviders(peerInfos []go_http_js_libp2p.PeerInfo) {
|
||||||
|
p.providers = peerInfos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *libp2pPublicRoomsProvider) Homeservers() []string {
|
||||||
|
result := make([]string, len(p.providers))
|
||||||
|
for i := range p.providers {
|
||||||
|
result[i] = p.providers[i].Id
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Shopify/sarama"
|
sarama "gopkg.in/Shopify/sarama.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
const usage = `Usage: %s
|
const usage = `Usage: %s
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ var (
|
||||||
// This needs to be high enough to account for the time it takes to create
|
// This needs to be high enough to account for the time it takes to create
|
||||||
// the postgres database tables which can take a while on travis.
|
// the postgres database tables which can take a while on travis.
|
||||||
timeoutString = defaulting(os.Getenv("TIMEOUT"), "60s")
|
timeoutString = defaulting(os.Getenv("TIMEOUT"), "60s")
|
||||||
|
// Timeout for http client
|
||||||
|
timeoutHTTPClient = defaulting(os.Getenv("TIMEOUT_HTTP"), "30s")
|
||||||
// The name of maintenance database to connect to in order to create the test database.
|
// The name of maintenance database to connect to in order to create the test database.
|
||||||
postgresDatabase = defaulting(os.Getenv("POSTGRES_DATABASE"), "postgres")
|
postgresDatabase = defaulting(os.Getenv("POSTGRES_DATABASE"), "postgres")
|
||||||
// The name of the test database to create.
|
// The name of the test database to create.
|
||||||
|
|
@ -68,7 +70,10 @@ func defaulting(value, defaultValue string) string {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
var timeout time.Duration
|
var (
|
||||||
|
timeout time.Duration
|
||||||
|
timeoutHTTP time.Duration
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
var err error
|
var err error
|
||||||
|
|
@ -76,6 +81,10 @@ func init() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
timeoutHTTP, err = time.ParseDuration(timeoutHTTPClient)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDatabase(database string) error {
|
func createDatabase(database string) error {
|
||||||
|
|
@ -199,7 +208,10 @@ func writeToRoomServer(input []string, roomserverURL string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
x := api.NewRoomserverInputAPIHTTP(roomserverURL, nil)
|
x, err := api.NewRoomserverInputAPIHTTP(roomserverURL, &http.Client{Timeout: timeoutHTTP})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return x.InputRoomEvents(context.Background(), &request, &response)
|
return x.InputRoomEvents(context.Background(), &request, &response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -258,7 +270,7 @@ func testRoomserver(input []string, wantOutput []string, checkQueries func(api.R
|
||||||
cmd.Args = []string{"dendrite-room-server", "--config", filepath.Join(dir, test.ConfigFile)}
|
cmd.Args = []string{"dendrite-room-server", "--config", filepath.Join(dir, test.ConfigFile)}
|
||||||
|
|
||||||
gotOutput, err := runAndReadFromTopic(cmd, cfg.RoomServerURL()+"/metrics", doInput, outputTopic, len(wantOutput), func() {
|
gotOutput, err := runAndReadFromTopic(cmd, cfg.RoomServerURL()+"/metrics", doInput, outputTopic, len(wantOutput), func() {
|
||||||
queryAPI := api.NewRoomserverQueryAPIHTTP("http://"+string(cfg.Listen.RoomServer), nil)
|
queryAPI, _ := api.NewRoomserverQueryAPIHTTP("http://"+string(cfg.Listen.RoomServer), &http.Client{Timeout: timeoutHTTP})
|
||||||
checkQueries(queryAPI)
|
checkQueries(queryAPI)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ func clientEventJSONForOutputRoomEvent(outputRoomEvent string) string {
|
||||||
panic("failed to unmarshal output room event: " + err.Error())
|
panic("failed to unmarshal output room event: " + err.Error())
|
||||||
}
|
}
|
||||||
clientEvs := gomatrixserverlib.ToClientEvents([]gomatrixserverlib.Event{
|
clientEvs := gomatrixserverlib.ToClientEvents([]gomatrixserverlib.Event{
|
||||||
out.NewRoomEvent.Event,
|
out.NewRoomEvent.Event.Event,
|
||||||
}, gomatrixserverlib.FormatSync)
|
}, gomatrixserverlib.FormatSync)
|
||||||
b, err := json.Marshal(clientEvs[0])
|
b, err := json.Marshal(clientEvs[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/ed25519"
|
"golang.org/x/crypto/ed25519"
|
||||||
|
|
||||||
|
|
@ -34,9 +36,9 @@ import (
|
||||||
|
|
||||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
|
eduServerAPI "github.com/matrix-org/dendrite/eduserver/api"
|
||||||
federationSenderAPI "github.com/matrix-org/dendrite/federationsender/api"
|
federationSenderAPI "github.com/matrix-org/dendrite/federationsender/api"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
typingServerAPI "github.com/matrix-org/dendrite/typingserver/api"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -51,6 +53,7 @@ type BaseDendrite struct {
|
||||||
|
|
||||||
// APIMux should be used to register new public matrix api endpoints
|
// APIMux should be used to register new public matrix api endpoints
|
||||||
APIMux *mux.Router
|
APIMux *mux.Router
|
||||||
|
httpClient *http.Client
|
||||||
Cfg *config.Dendrite
|
Cfg *config.Dendrite
|
||||||
KafkaConsumer sarama.Consumer
|
KafkaConsumer sarama.Consumer
|
||||||
KafkaProducer sarama.SyncProducer
|
KafkaProducer sarama.SyncProducer
|
||||||
|
|
@ -68,13 +71,22 @@ func NewBaseDendrite(cfg *config.Dendrite, componentName string) *BaseDendrite {
|
||||||
logrus.WithError(err).Panicf("failed to start opentracing")
|
logrus.WithError(err).Panicf("failed to start opentracing")
|
||||||
}
|
}
|
||||||
|
|
||||||
kafkaConsumer, kafkaProducer := setupKafka(cfg)
|
var kafkaConsumer sarama.Consumer
|
||||||
|
var kafkaProducer sarama.SyncProducer
|
||||||
|
if cfg.Kafka.UseNaffka {
|
||||||
|
kafkaConsumer, kafkaProducer = setupNaffka(cfg)
|
||||||
|
} else {
|
||||||
|
kafkaConsumer, kafkaProducer = setupKafka(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultHTTPTimeout = 30 * time.Second
|
||||||
|
|
||||||
return &BaseDendrite{
|
return &BaseDendrite{
|
||||||
componentName: componentName,
|
componentName: componentName,
|
||||||
tracerCloser: closer,
|
tracerCloser: closer,
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
APIMux: mux.NewRouter().UseEncodedPath(),
|
APIMux: mux.NewRouter().UseEncodedPath(),
|
||||||
|
httpClient: &http.Client{Timeout: defaultHTTPTimeout},
|
||||||
KafkaConsumer: kafkaConsumer,
|
KafkaConsumer: kafkaConsumer,
|
||||||
KafkaProducer: kafkaProducer,
|
KafkaProducer: kafkaProducer,
|
||||||
}
|
}
|
||||||
|
|
@ -88,7 +100,11 @@ func (b *BaseDendrite) Close() error {
|
||||||
// CreateHTTPAppServiceAPIs returns the QueryAPI for hitting the appservice
|
// CreateHTTPAppServiceAPIs returns the QueryAPI for hitting the appservice
|
||||||
// component over HTTP.
|
// component over HTTP.
|
||||||
func (b *BaseDendrite) CreateHTTPAppServiceAPIs() appserviceAPI.AppServiceQueryAPI {
|
func (b *BaseDendrite) CreateHTTPAppServiceAPIs() appserviceAPI.AppServiceQueryAPI {
|
||||||
return appserviceAPI.NewAppServiceQueryAPIHTTP(b.Cfg.AppServiceURL(), nil)
|
a, err := appserviceAPI.NewAppServiceQueryAPIHTTP(b.Cfg.AppServiceURL(), b.httpClient)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Panic("CreateHTTPAppServiceAPIs failed")
|
||||||
|
}
|
||||||
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateHTTPRoomserverAPIs returns the AliasAPI, InputAPI and QueryAPI for hitting
|
// CreateHTTPRoomserverAPIs returns the AliasAPI, InputAPI and QueryAPI for hitting
|
||||||
|
|
@ -98,27 +114,45 @@ func (b *BaseDendrite) CreateHTTPRoomserverAPIs() (
|
||||||
roomserverAPI.RoomserverInputAPI,
|
roomserverAPI.RoomserverInputAPI,
|
||||||
roomserverAPI.RoomserverQueryAPI,
|
roomserverAPI.RoomserverQueryAPI,
|
||||||
) {
|
) {
|
||||||
alias := roomserverAPI.NewRoomserverAliasAPIHTTP(b.Cfg.RoomServerURL(), nil)
|
|
||||||
input := roomserverAPI.NewRoomserverInputAPIHTTP(b.Cfg.RoomServerURL(), nil)
|
alias, err := roomserverAPI.NewRoomserverAliasAPIHTTP(b.Cfg.RoomServerURL(), b.httpClient)
|
||||||
query := roomserverAPI.NewRoomserverQueryAPIHTTP(b.Cfg.RoomServerURL(), nil)
|
if err != nil {
|
||||||
|
logrus.WithError(err).Panic("NewRoomserverAliasAPIHTTP failed")
|
||||||
|
}
|
||||||
|
input, err := roomserverAPI.NewRoomserverInputAPIHTTP(b.Cfg.RoomServerURL(), b.httpClient)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Panic("NewRoomserverInputAPIHTTP failed", b.httpClient)
|
||||||
|
}
|
||||||
|
query, err := roomserverAPI.NewRoomserverQueryAPIHTTP(b.Cfg.RoomServerURL(), nil)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Panic("NewRoomserverQueryAPIHTTP failed", b.httpClient)
|
||||||
|
}
|
||||||
return alias, input, query
|
return alias, input, query
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateHTTPTypingServerAPIs returns typingInputAPI for hitting the typing
|
// CreateHTTPEDUServerAPIs returns eduInputAPI for hitting the EDU
|
||||||
// server over HTTP
|
// server over HTTP
|
||||||
func (b *BaseDendrite) CreateHTTPTypingServerAPIs() typingServerAPI.TypingServerInputAPI {
|
func (b *BaseDendrite) CreateHTTPEDUServerAPIs() eduServerAPI.EDUServerInputAPI {
|
||||||
return typingServerAPI.NewTypingServerInputAPIHTTP(b.Cfg.TypingServerURL(), nil)
|
e, err := eduServerAPI.NewEDUServerInputAPIHTTP(b.Cfg.EDUServerURL(), nil)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Panic("NewEDUServerInputAPIHTTP failed", b.httpClient)
|
||||||
|
}
|
||||||
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateHTTPFederationSenderAPIs returns FederationSenderQueryAPI for hitting
|
// CreateHTTPFederationSenderAPIs returns FederationSenderQueryAPI for hitting
|
||||||
// the federation sender over HTTP
|
// the federation sender over HTTP
|
||||||
func (b *BaseDendrite) CreateHTTPFederationSenderAPIs() federationSenderAPI.FederationSenderQueryAPI {
|
func (b *BaseDendrite) CreateHTTPFederationSenderAPIs() federationSenderAPI.FederationSenderQueryAPI {
|
||||||
return federationSenderAPI.NewFederationSenderQueryAPIHTTP(b.Cfg.FederationSenderURL(), nil)
|
f, err := federationSenderAPI.NewFederationSenderQueryAPIHTTP(b.Cfg.FederationSenderURL(), nil)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Panic("NewFederationSenderQueryAPIHTTP failed", b.httpClient)
|
||||||
|
}
|
||||||
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateDeviceDB creates a new instance of the device database. Should only be
|
// CreateDeviceDB creates a new instance of the device database. Should only be
|
||||||
// called once per component.
|
// called once per component.
|
||||||
func (b *BaseDendrite) CreateDeviceDB() *devices.Database {
|
func (b *BaseDendrite) CreateDeviceDB() devices.Database {
|
||||||
db, err := devices.NewDatabase(string(b.Cfg.Database.Device), b.Cfg.Matrix.ServerName)
|
db, err := devices.NewDatabase(string(b.Cfg.Database.Device), b.Cfg.Matrix.ServerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Panicf("failed to connect to devices db")
|
logrus.WithError(err).Panicf("failed to connect to devices db")
|
||||||
|
|
@ -129,7 +163,7 @@ func (b *BaseDendrite) CreateDeviceDB() *devices.Database {
|
||||||
|
|
||||||
// CreateAccountsDB creates a new instance of the accounts database. Should only
|
// CreateAccountsDB creates a new instance of the accounts database. Should only
|
||||||
// be called once per component.
|
// be called once per component.
|
||||||
func (b *BaseDendrite) CreateAccountsDB() *accounts.Database {
|
func (b *BaseDendrite) CreateAccountsDB() accounts.Database {
|
||||||
db, err := accounts.NewDatabase(string(b.Cfg.Database.Account), b.Cfg.Matrix.ServerName)
|
db, err := accounts.NewDatabase(string(b.Cfg.Database.Account), b.Cfg.Matrix.ServerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Panicf("failed to connect to accounts db")
|
logrus.WithError(err).Panicf("failed to connect to accounts db")
|
||||||
|
|
@ -186,28 +220,8 @@ func (b *BaseDendrite) SetupAndServeHTTP(bindaddr string, listenaddr string) {
|
||||||
logrus.Infof("Stopped %s server on %s", b.componentName, addr)
|
logrus.Infof("Stopped %s server on %s", b.componentName, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// setupKafka creates kafka consumer/producer pair from the config. Checks if
|
// setupKafka creates kafka consumer/producer pair from the config.
|
||||||
// should use naffka.
|
|
||||||
func setupKafka(cfg *config.Dendrite) (sarama.Consumer, sarama.SyncProducer) {
|
func setupKafka(cfg *config.Dendrite) (sarama.Consumer, sarama.SyncProducer) {
|
||||||
if cfg.Kafka.UseNaffka {
|
|
||||||
db, err := sql.Open("postgres", string(cfg.Database.Naffka))
|
|
||||||
if err != nil {
|
|
||||||
logrus.WithError(err).Panic("Failed to open naffka database")
|
|
||||||
}
|
|
||||||
|
|
||||||
naffkaDB, err := naffka.NewPostgresqlDatabase(db)
|
|
||||||
if err != nil {
|
|
||||||
logrus.WithError(err).Panic("Failed to setup naffka database")
|
|
||||||
}
|
|
||||||
|
|
||||||
naff, err := naffka.New(naffkaDB)
|
|
||||||
if err != nil {
|
|
||||||
logrus.WithError(err).Panic("Failed to setup naffka")
|
|
||||||
}
|
|
||||||
|
|
||||||
return naff, naff
|
|
||||||
}
|
|
||||||
|
|
||||||
consumer, err := sarama.NewConsumer(cfg.Kafka.Addresses, nil)
|
consumer, err := sarama.NewConsumer(cfg.Kafka.Addresses, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Panic("failed to start kafka consumer")
|
logrus.WithError(err).Panic("failed to start kafka consumer")
|
||||||
|
|
@ -220,3 +234,44 @@ func setupKafka(cfg *config.Dendrite) (sarama.Consumer, sarama.SyncProducer) {
|
||||||
|
|
||||||
return consumer, producer
|
return consumer, producer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setupNaffka creates kafka consumer/producer pair from the config.
|
||||||
|
func setupNaffka(cfg *config.Dendrite) (sarama.Consumer, sarama.SyncProducer) {
|
||||||
|
var err error
|
||||||
|
var db *sql.DB
|
||||||
|
var naffkaDB *naffka.DatabaseImpl
|
||||||
|
|
||||||
|
uri, err := url.Parse(string(cfg.Database.Naffka))
|
||||||
|
if err != nil || uri.Scheme == "file" {
|
||||||
|
db, err = sql.Open(common.SQLiteDriverName(), string(cfg.Database.Naffka))
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Panic("Failed to open naffka database")
|
||||||
|
}
|
||||||
|
|
||||||
|
naffkaDB, err = naffka.NewSqliteDatabase(db)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Panic("Failed to setup naffka database")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
db, err = sql.Open("postgres", string(cfg.Database.Naffka))
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Panic("Failed to open naffka database")
|
||||||
|
}
|
||||||
|
|
||||||
|
naffkaDB, err = naffka.NewPostgresqlDatabase(db)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Panic("Failed to setup naffka database")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if naffkaDB == nil {
|
||||||
|
panic("naffka connection string not understood")
|
||||||
|
}
|
||||||
|
|
||||||
|
naff, err := naffka.New(naffkaDB)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Panic("Failed to setup naffka")
|
||||||
|
}
|
||||||
|
|
||||||
|
return naff, naff
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ type Dendrite struct {
|
||||||
OutputRoomEvent Topic `yaml:"output_room_event"`
|
OutputRoomEvent Topic `yaml:"output_room_event"`
|
||||||
// Topic for sending account data from client API to sync API
|
// Topic for sending account data from client API to sync API
|
||||||
OutputClientData Topic `yaml:"output_client_data"`
|
OutputClientData Topic `yaml:"output_client_data"`
|
||||||
// Topic for typingserver/api.OutputTypingEvent events.
|
// Topic for eduserver/api.OutputTypingEvent events.
|
||||||
OutputTypingEvent Topic `yaml:"output_typing_event"`
|
OutputTypingEvent Topic `yaml:"output_typing_event"`
|
||||||
// Topic for user updates (profile, presence)
|
// Topic for user updates (profile, presence)
|
||||||
UserUpdates Topic `yaml:"user_updates"`
|
UserUpdates Topic `yaml:"user_updates"`
|
||||||
|
|
@ -206,7 +206,7 @@ type Dendrite struct {
|
||||||
RoomServer Address `yaml:"room_server"`
|
RoomServer Address `yaml:"room_server"`
|
||||||
FederationSender Address `yaml:"federation_sender"`
|
FederationSender Address `yaml:"federation_sender"`
|
||||||
PublicRoomsAPI Address `yaml:"public_rooms_api"`
|
PublicRoomsAPI Address `yaml:"public_rooms_api"`
|
||||||
TypingServer Address `yaml:"typing_server"`
|
EDUServer Address `yaml:"edu_server"`
|
||||||
} `yaml:"bind"`
|
} `yaml:"bind"`
|
||||||
|
|
||||||
// The addresses for talking to other microservices.
|
// The addresses for talking to other microservices.
|
||||||
|
|
@ -219,11 +219,13 @@ type Dendrite struct {
|
||||||
RoomServer Address `yaml:"room_server"`
|
RoomServer Address `yaml:"room_server"`
|
||||||
FederationSender Address `yaml:"federation_sender"`
|
FederationSender Address `yaml:"federation_sender"`
|
||||||
PublicRoomsAPI Address `yaml:"public_rooms_api"`
|
PublicRoomsAPI Address `yaml:"public_rooms_api"`
|
||||||
TypingServer Address `yaml:"typing_server"`
|
EDUServer Address `yaml:"edu_server"`
|
||||||
} `yaml:"listen"`
|
} `yaml:"listen"`
|
||||||
|
|
||||||
// The config for tracing the dendrite servers.
|
// The config for tracing the dendrite servers.
|
||||||
Tracing struct {
|
Tracing struct {
|
||||||
|
// Set to true to enable tracer hooks. If false, no tracing is set up.
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
// The config for the jaeger opentracing reporter.
|
// The config for the jaeger opentracing reporter.
|
||||||
Jaeger jaegerconfig.Configuration `yaml:"jaeger"`
|
Jaeger jaegerconfig.Configuration `yaml:"jaeger"`
|
||||||
} `yaml:"tracing"`
|
} `yaml:"tracing"`
|
||||||
|
|
@ -365,7 +367,7 @@ func loadConfig(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
config.setDefaults()
|
config.SetDefaults()
|
||||||
|
|
||||||
if err = config.check(monolithic); err != nil {
|
if err = config.check(monolithic); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -398,7 +400,7 @@ func loadConfig(
|
||||||
config.Media.AbsBasePath = Path(absPath(basePath, config.Media.BasePath))
|
config.Media.AbsBasePath = Path(absPath(basePath, config.Media.BasePath))
|
||||||
|
|
||||||
// Generate data from config options
|
// Generate data from config options
|
||||||
err = config.derive()
|
err = config.Derive()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -406,9 +408,9 @@ func loadConfig(
|
||||||
return &config, nil
|
return &config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// derive generates data that is derived from various values provided in
|
// Derive generates data that is derived from various values provided in
|
||||||
// the config file.
|
// the config file.
|
||||||
func (config *Dendrite) derive() error {
|
func (config *Dendrite) Derive() error {
|
||||||
// Determine registrations flows based off config values
|
// Determine registrations flows based off config values
|
||||||
|
|
||||||
config.Derived.Registration.Params = make(map[string]interface{})
|
config.Derived.Registration.Params = make(map[string]interface{})
|
||||||
|
|
@ -433,8 +435,8 @@ func (config *Dendrite) derive() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// setDefaults sets default config values if they are not explicitly set.
|
// SetDefaults sets default config values if they are not explicitly set.
|
||||||
func (config *Dendrite) setDefaults() {
|
func (config *Dendrite) SetDefaults() {
|
||||||
if config.Matrix.KeyValidityPeriod == 0 {
|
if config.Matrix.KeyValidityPeriod == 0 {
|
||||||
config.Matrix.KeyValidityPeriod = 24 * time.Hour
|
config.Matrix.KeyValidityPeriod = 24 * time.Hour
|
||||||
}
|
}
|
||||||
|
|
@ -569,7 +571,7 @@ func (config *Dendrite) checkListen(configErrs *configErrors) {
|
||||||
checkNotEmpty(configErrs, "listen.federation_api", string(config.Listen.FederationAPI))
|
checkNotEmpty(configErrs, "listen.federation_api", string(config.Listen.FederationAPI))
|
||||||
checkNotEmpty(configErrs, "listen.sync_api", string(config.Listen.SyncAPI))
|
checkNotEmpty(configErrs, "listen.sync_api", string(config.Listen.SyncAPI))
|
||||||
checkNotEmpty(configErrs, "listen.room_server", string(config.Listen.RoomServer))
|
checkNotEmpty(configErrs, "listen.room_server", string(config.Listen.RoomServer))
|
||||||
checkNotEmpty(configErrs, "listen.typing_server", string(config.Listen.TypingServer))
|
checkNotEmpty(configErrs, "listen.edu_server", string(config.Listen.EDUServer))
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkLogging verifies the parameters logging.* are valid.
|
// checkLogging verifies the parameters logging.* are valid.
|
||||||
|
|
@ -667,7 +669,7 @@ func fingerprintPEM(data []byte) *gomatrixserverlib.TLSFingerprint {
|
||||||
|
|
||||||
// AppServiceURL returns a HTTP URL for where the appservice component is listening.
|
// AppServiceURL returns a HTTP URL for where the appservice component is listening.
|
||||||
func (config *Dendrite) AppServiceURL() string {
|
func (config *Dendrite) AppServiceURL() string {
|
||||||
// Hard code the roomserver to talk HTTP for now.
|
// Hard code the appservice server to talk HTTP for now.
|
||||||
// If we support HTTPS we need to think of a practical way to do certificate validation.
|
// If we support HTTPS we need to think of a practical way to do certificate validation.
|
||||||
// People setting up servers shouldn't need to get a certificate valid for the public
|
// People setting up servers shouldn't need to get a certificate valid for the public
|
||||||
// internet for an internal API.
|
// internet for an internal API.
|
||||||
|
|
@ -683,18 +685,18 @@ func (config *Dendrite) RoomServerURL() string {
|
||||||
return "http://" + string(config.Listen.RoomServer)
|
return "http://" + string(config.Listen.RoomServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TypingServerURL returns an HTTP URL for where the typing server is listening.
|
// EDUServerURL returns an HTTP URL for where the EDU server is listening.
|
||||||
func (config *Dendrite) TypingServerURL() string {
|
func (config *Dendrite) EDUServerURL() string {
|
||||||
// Hard code the typing server to talk HTTP for now.
|
// Hard code the EDU server to talk HTTP for now.
|
||||||
// If we support HTTPS we need to think of a practical way to do certificate validation.
|
// If we support HTTPS we need to think of a practical way to do certificate validation.
|
||||||
// People setting up servers shouldn't need to get a certificate valid for the public
|
// People setting up servers shouldn't need to get a certificate valid for the public
|
||||||
// internet for an internal API.
|
// internet for an internal API.
|
||||||
return "http://" + string(config.Listen.TypingServer)
|
return "http://" + string(config.Listen.EDUServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FederationSenderURL returns an HTTP URL for where the federation sender is listening.
|
// FederationSenderURL returns an HTTP URL for where the federation sender is listening.
|
||||||
func (config *Dendrite) FederationSenderURL() string {
|
func (config *Dendrite) FederationSenderURL() string {
|
||||||
// Hard code the typing server to talk HTTP for now.
|
// Hard code the federation sender server to talk HTTP for now.
|
||||||
// If we support HTTPS we need to think of a practical way to do certificate validation.
|
// If we support HTTPS we need to think of a practical way to do certificate validation.
|
||||||
// People setting up servers shouldn't need to get a certificate valid for the public
|
// People setting up servers shouldn't need to get a certificate valid for the public
|
||||||
// internet for an internal API.
|
// internet for an internal API.
|
||||||
|
|
@ -703,6 +705,9 @@ func (config *Dendrite) FederationSenderURL() string {
|
||||||
|
|
||||||
// SetupTracing configures the opentracing using the supplied configuration.
|
// SetupTracing configures the opentracing using the supplied configuration.
|
||||||
func (config *Dendrite) SetupTracing(serviceName string) (closer io.Closer, err error) {
|
func (config *Dendrite) SetupTracing(serviceName string) (closer io.Closer, err error) {
|
||||||
|
if !config.Tracing.Enabled {
|
||||||
|
return ioutil.NopCloser(bytes.NewReader([]byte{})), nil
|
||||||
|
}
|
||||||
return config.Tracing.Jaeger.InitGlobalTracer(
|
return config.Tracing.Jaeger.InitGlobalTracer(
|
||||||
serviceName,
|
serviceName,
|
||||||
jaegerconfig.Logger(logrusLogger{logrus.StandardLogger()}),
|
jaegerconfig.Logger(logrusLogger{logrus.StandardLogger()}),
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ listen:
|
||||||
sync_api: "localhost:7773"
|
sync_api: "localhost:7773"
|
||||||
media_api: "localhost:7774"
|
media_api: "localhost:7774"
|
||||||
appservice_api: "localhost:7777"
|
appservice_api: "localhost:7777"
|
||||||
typing_server: "localhost:7778"
|
edu_server: "localhost:7778"
|
||||||
logging:
|
logging:
|
||||||
- type: "file"
|
- type: "file"
|
||||||
level: "info"
|
level: "info"
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ func (c *ContinualConsumer) consumePartition(pc sarama.PartitionConsumer) {
|
||||||
msgErr := c.ProcessMessage(message)
|
msgErr := c.ProcessMessage(message)
|
||||||
// Advance our position in the stream so that we will start at the right position after a restart.
|
// Advance our position in the stream so that we will start at the right position after a restart.
|
||||||
if err := c.PartitionStore.SetPartitionOffset(context.TODO(), c.Topic, message.Partition, message.Offset); err != nil {
|
if err := c.PartitionStore.SetPartitionOffset(context.TODO(), c.Topic, message.Partition, message.Offset); err != nil {
|
||||||
panic(fmt.Errorf("the ContinualConsumer failed to SetPartitionOffset: %s", err))
|
panic(fmt.Errorf("the ContinualConsumer failed to SetPartitionOffset: %w", err))
|
||||||
}
|
}
|
||||||
// Shutdown if we were told to do so.
|
// Shutdown if we were told to do so.
|
||||||
if msgErr == ErrShutdown {
|
if msgErr == ErrShutdown {
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import (
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrRoomNoExists is returned when trying to lookup the state of a room that
|
// ErrRoomNoExists is returned when trying to lookup the state of a room that
|
||||||
|
|
@ -39,16 +38,23 @@ var ErrRoomNoExists = errors.New("Room does not exist")
|
||||||
// Returns an error if something else went wrong
|
// Returns an error if something else went wrong
|
||||||
func BuildEvent(
|
func BuildEvent(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
builder *gomatrixserverlib.EventBuilder, cfg config.Dendrite, evTime time.Time,
|
builder *gomatrixserverlib.EventBuilder, cfg *config.Dendrite, evTime time.Time,
|
||||||
queryAPI api.RoomserverQueryAPI, queryRes *api.QueryLatestEventsAndStateResponse,
|
queryAPI api.RoomserverQueryAPI, queryRes *api.QueryLatestEventsAndStateResponse,
|
||||||
) (*gomatrixserverlib.Event, error) {
|
) (*gomatrixserverlib.Event, error) {
|
||||||
|
if queryRes == nil {
|
||||||
|
queryRes = &api.QueryLatestEventsAndStateResponse{}
|
||||||
|
}
|
||||||
|
|
||||||
err := AddPrevEventsToEvent(ctx, builder, queryAPI, queryRes)
|
err := AddPrevEventsToEvent(ctx, builder, queryAPI, queryRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// This can pass through a ErrRoomNoExists to the caller
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
eventID := fmt.Sprintf("$%s:%s", util.RandomString(16), cfg.Matrix.ServerName)
|
event, err := builder.Build(
|
||||||
event, err := builder.Build(eventID, evTime, cfg.Matrix.ServerName, cfg.Matrix.KeyID, cfg.Matrix.PrivateKey)
|
evTime, cfg.Matrix.ServerName, cfg.Matrix.KeyID,
|
||||||
|
cfg.Matrix.PrivateKey, queryRes.RoomVersion,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -64,7 +70,7 @@ func AddPrevEventsToEvent(
|
||||||
) error {
|
) error {
|
||||||
eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(builder)
|
eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(builder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("gomatrixserverlib.StateNeededForEventBuilder: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask the roomserver for information about this room
|
// Ask the roomserver for information about this room
|
||||||
|
|
@ -72,34 +78,69 @@ func AddPrevEventsToEvent(
|
||||||
RoomID: builder.RoomID,
|
RoomID: builder.RoomID,
|
||||||
StateToFetch: eventsNeeded.Tuples(),
|
StateToFetch: eventsNeeded.Tuples(),
|
||||||
}
|
}
|
||||||
if queryRes == nil {
|
|
||||||
queryRes = &api.QueryLatestEventsAndStateResponse{}
|
|
||||||
}
|
|
||||||
if err = queryAPI.QueryLatestEventsAndState(ctx, &queryReq, queryRes); err != nil {
|
if err = queryAPI.QueryLatestEventsAndState(ctx, &queryReq, queryRes); err != nil {
|
||||||
return err
|
return fmt.Errorf("queryAPI.QueryLatestEventsAndState: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !queryRes.RoomExists {
|
if !queryRes.RoomExists {
|
||||||
return ErrRoomNoExists
|
return ErrRoomNoExists
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eventFormat, err := queryRes.RoomVersion.EventFormat()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("queryRes.RoomVersion.EventFormat: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
builder.Depth = queryRes.Depth
|
builder.Depth = queryRes.Depth
|
||||||
builder.PrevEvents = queryRes.LatestEvents
|
|
||||||
|
|
||||||
authEvents := gomatrixserverlib.NewAuthEvents(nil)
|
authEvents := gomatrixserverlib.NewAuthEvents(nil)
|
||||||
|
|
||||||
for i := range queryRes.StateEvents {
|
for i := range queryRes.StateEvents {
|
||||||
err = authEvents.AddEvent(&queryRes.StateEvents[i])
|
err = authEvents.AddEvent(&queryRes.StateEvents[i].Event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("authEvents.AddEvent: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
refs, err := eventsNeeded.AuthEventReferences(&authEvents)
|
refs, err := eventsNeeded.AuthEventReferences(&authEvents)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("eventsNeeded.AuthEventReferences: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
truncAuth, truncPrev := truncateAuthAndPrevEvents(refs, queryRes.LatestEvents)
|
||||||
|
switch eventFormat {
|
||||||
|
case gomatrixserverlib.EventFormatV1:
|
||||||
|
builder.AuthEvents = truncAuth
|
||||||
|
builder.PrevEvents = truncPrev
|
||||||
|
case gomatrixserverlib.EventFormatV2:
|
||||||
|
v2AuthRefs, v2PrevRefs := []string{}, []string{}
|
||||||
|
for _, ref := range truncAuth {
|
||||||
|
v2AuthRefs = append(v2AuthRefs, ref.EventID)
|
||||||
|
}
|
||||||
|
for _, ref := range truncPrev {
|
||||||
|
v2PrevRefs = append(v2PrevRefs, ref.EventID)
|
||||||
|
}
|
||||||
|
builder.AuthEvents = v2AuthRefs
|
||||||
|
builder.PrevEvents = v2PrevRefs
|
||||||
}
|
}
|
||||||
builder.AuthEvents = refs
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// truncateAuthAndPrevEvents limits the number of events we add into
|
||||||
|
// an event as prev_events or auth_events.
|
||||||
|
// NOTSPEC: The limits here feel a bit arbitrary but they are currently
|
||||||
|
// here because of https://github.com/matrix-org/matrix-doc/issues/2307
|
||||||
|
// and because Synapse will just drop events that don't comply.
|
||||||
|
func truncateAuthAndPrevEvents(auth, prev []gomatrixserverlib.EventReference) (
|
||||||
|
truncAuth, truncPrev []gomatrixserverlib.EventReference,
|
||||||
|
) {
|
||||||
|
truncAuth, truncPrev = auth, prev
|
||||||
|
if len(truncAuth) > 10 {
|
||||||
|
truncAuth = truncAuth[:10]
|
||||||
|
}
|
||||||
|
if len(truncPrev) > 20 {
|
||||||
|
truncPrev = truncPrev[:20]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,10 @@ func MakeAuthAPI(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return *err
|
return *err
|
||||||
}
|
}
|
||||||
|
// add the user ID to the logger
|
||||||
|
logger := util.GetLogger((req.Context()))
|
||||||
|
logger = logger.WithField("user_id", device.UserID)
|
||||||
|
req = req.WithContext(util.ContextWithLogger(req.Context(), logger))
|
||||||
|
|
||||||
return f(req, device)
|
return f(req, device)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
common/keydb/interface.go
Normal file
13
common/keydb/interface.go
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
package keydb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Database interface {
|
||||||
|
FetcherName() string
|
||||||
|
FetchKeys(ctx context.Context, requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error)
|
||||||
|
StoreKeys(ctx context.Context, keyMap map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult) error
|
||||||
|
}
|
||||||
|
|
@ -12,24 +12,20 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build !wasm
|
||||||
|
|
||||||
package keydb
|
package keydb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"golang.org/x/crypto/ed25519"
|
"golang.org/x/crypto/ed25519"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/common/keydb/postgres"
|
"github.com/matrix-org/dendrite/common/keydb/postgres"
|
||||||
|
"github.com/matrix-org/dendrite/common/keydb/sqlite3"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Database interface {
|
|
||||||
FetcherName() string
|
|
||||||
FetchKeys(ctx context.Context, requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error)
|
|
||||||
StoreKeys(ctx context.Context, keyMap map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDatabase opens a database connection.
|
// NewDatabase opens a database connection.
|
||||||
func NewDatabase(
|
func NewDatabase(
|
||||||
dataSourceName string,
|
dataSourceName string,
|
||||||
|
|
@ -44,6 +40,8 @@ func NewDatabase(
|
||||||
switch uri.Scheme {
|
switch uri.Scheme {
|
||||||
case "postgres":
|
case "postgres":
|
||||||
return postgres.NewDatabase(dataSourceName, serverName, serverKey, serverKeyID)
|
return postgres.NewDatabase(dataSourceName, serverName, serverKey, serverKeyID)
|
||||||
|
case "file":
|
||||||
|
return sqlite3.NewDatabase(dataSourceName, serverName, serverKey, serverKeyID)
|
||||||
default:
|
default:
|
||||||
return postgres.NewDatabase(dataSourceName, serverName, serverKey, serverKeyID)
|
return postgres.NewDatabase(dataSourceName, serverName, serverKey, serverKeyID)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
46
common/keydb/keydb_wasm.go
Normal file
46
common/keydb/keydb_wasm.go
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package keydb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/common/keydb/sqlite3"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewDatabase opens a database connection.
|
||||||
|
func NewDatabase(
|
||||||
|
dataSourceName string,
|
||||||
|
serverName gomatrixserverlib.ServerName,
|
||||||
|
serverKey ed25519.PublicKey,
|
||||||
|
serverKeyID gomatrixserverlib.KeyID,
|
||||||
|
) (Database, error) {
|
||||||
|
uri, err := url.Parse(dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch uri.Scheme {
|
||||||
|
case "postgres":
|
||||||
|
return nil, fmt.Errorf("Cannot use postgres implementation")
|
||||||
|
case "file":
|
||||||
|
return sqlite3.NewDatabase(dataSourceName, serverName, serverKey, serverKeyID)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Cannot use postgres implementation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
)
|
)
|
||||||
|
|
@ -91,7 +93,7 @@ func (s *serverKeyStatements) bulkSelectServerKeys(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close() // nolint: errcheck
|
defer common.CloseAndLogIfError(ctx, rows, "bulkSelectServerKeys: rows.close() failed")
|
||||||
results := map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult{}
|
results := map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var serverName string
|
var serverName string
|
||||||
|
|
@ -117,7 +119,7 @@ func (s *serverKeyStatements) bulkSelectServerKeys(
|
||||||
ExpiredTS: gomatrixserverlib.Timestamp(expiredTS),
|
ExpiredTS: gomatrixserverlib.Timestamp(expiredTS),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return results, nil
|
return results, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *serverKeyStatements) upsertServerKeys(
|
func (s *serverKeyStatements) upsertServerKeys(
|
||||||
|
|
|
||||||
116
common/keydb/sqlite3/keydb.go
Normal file
116
common/keydb/sqlite3/keydb.go
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Database implements gomatrixserverlib.KeyDatabase and is used to store
|
||||||
|
// the public keys for other matrix servers.
|
||||||
|
type Database struct {
|
||||||
|
statements serverKeyStatements
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDatabase prepares a new key database.
|
||||||
|
// It creates the necessary tables if they don't already exist.
|
||||||
|
// It prepares all the SQL statements that it will use.
|
||||||
|
// Returns an error if there was a problem talking to the database.
|
||||||
|
func NewDatabase(
|
||||||
|
dataSourceName string,
|
||||||
|
serverName gomatrixserverlib.ServerName,
|
||||||
|
serverKey ed25519.PublicKey,
|
||||||
|
serverKeyID gomatrixserverlib.KeyID,
|
||||||
|
) (*Database, error) {
|
||||||
|
db, err := sql.Open(common.SQLiteDriverName(), dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
d := &Database{}
|
||||||
|
err = d.statements.prepare(db)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Store our own keys so that we don't end up making HTTP requests to find our
|
||||||
|
// own keys
|
||||||
|
index := gomatrixserverlib.PublicKeyLookupRequest{
|
||||||
|
ServerName: serverName,
|
||||||
|
KeyID: serverKeyID,
|
||||||
|
}
|
||||||
|
value := gomatrixserverlib.PublicKeyLookupResult{
|
||||||
|
VerifyKey: gomatrixserverlib.VerifyKey{
|
||||||
|
Key: gomatrixserverlib.Base64String(serverKey),
|
||||||
|
},
|
||||||
|
ValidUntilTS: math.MaxUint64 >> 1,
|
||||||
|
ExpiredTS: gomatrixserverlib.PublicKeyNotExpired,
|
||||||
|
}
|
||||||
|
err = d.StoreKeys(
|
||||||
|
context.Background(),
|
||||||
|
map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult{
|
||||||
|
index: value,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetcherName implements KeyFetcher
|
||||||
|
func (d Database) FetcherName() string {
|
||||||
|
return "KeyDatabase"
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchKeys implements gomatrixserverlib.KeyDatabase
|
||||||
|
func (d *Database) FetchKeys(
|
||||||
|
ctx context.Context,
|
||||||
|
requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp,
|
||||||
|
) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) {
|
||||||
|
return d.statements.bulkSelectServerKeys(ctx, requests)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreKeys implements gomatrixserverlib.KeyDatabase
|
||||||
|
func (d *Database) StoreKeys(
|
||||||
|
ctx context.Context,
|
||||||
|
keyMap map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult,
|
||||||
|
) error {
|
||||||
|
// TODO: Inserting all the keys within a single transaction may
|
||||||
|
// be more efficient since the transaction overhead can be quite
|
||||||
|
// high for a single insert statement.
|
||||||
|
var lastErr error
|
||||||
|
for request, keys := range keyMap {
|
||||||
|
if err := d.statements.upsertServerKeys(ctx, request, keys); err != nil {
|
||||||
|
// Rather than returning immediately on error we try to insert the
|
||||||
|
// remaining keys.
|
||||||
|
// Since we are inserting the keys outside of a transaction it is
|
||||||
|
// possible for some of the inserts to succeed even though some
|
||||||
|
// of the inserts have failed.
|
||||||
|
// Ensuring that we always insert all the keys we can means that
|
||||||
|
// this behaviour won't depend on the iteration order of the map.
|
||||||
|
lastErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lastErr
|
||||||
|
}
|
||||||
176
common/keydb/sqlite3/server_key_table.go
Normal file
176
common/keydb/sqlite3/server_key_table.go
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
lru "github.com/hashicorp/golang-lru"
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const serverKeysSchema = `
|
||||||
|
-- A cache of signing keys downloaded from remote servers.
|
||||||
|
CREATE TABLE IF NOT EXISTS keydb_server_keys (
|
||||||
|
-- The name of the matrix server the key is for.
|
||||||
|
server_name TEXT NOT NULL,
|
||||||
|
-- The ID of the server key.
|
||||||
|
server_key_id TEXT NOT NULL,
|
||||||
|
-- Combined server name and key ID separated by the ASCII unit separator
|
||||||
|
-- to make it easier to run bulk queries.
|
||||||
|
server_name_and_key_id TEXT NOT NULL,
|
||||||
|
-- When the key is valid until as a millisecond timestamp.
|
||||||
|
-- 0 if this is an expired key (in which case expired_ts will be non-zero)
|
||||||
|
valid_until_ts BIGINT NOT NULL,
|
||||||
|
-- When the key expired as a millisecond timestamp.
|
||||||
|
-- 0 if this is an active key (in which case valid_until_ts will be non-zero)
|
||||||
|
expired_ts BIGINT NOT NULL,
|
||||||
|
-- The base64-encoded public key.
|
||||||
|
server_key TEXT NOT NULL,
|
||||||
|
UNIQUE (server_name, server_key_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS keydb_server_name_and_key_id ON keydb_server_keys (server_name_and_key_id);
|
||||||
|
`
|
||||||
|
|
||||||
|
const bulkSelectServerKeysSQL = "" +
|
||||||
|
"SELECT server_name, server_key_id, valid_until_ts, expired_ts, " +
|
||||||
|
" server_key FROM keydb_server_keys" +
|
||||||
|
" WHERE server_name_and_key_id IN ($1)"
|
||||||
|
|
||||||
|
const upsertServerKeysSQL = "" +
|
||||||
|
"INSERT INTO keydb_server_keys (server_name, server_key_id," +
|
||||||
|
" server_name_and_key_id, valid_until_ts, expired_ts, server_key)" +
|
||||||
|
" VALUES ($1, $2, $3, $4, $5, $6)" +
|
||||||
|
" ON CONFLICT (server_name, server_key_id)" +
|
||||||
|
" DO UPDATE SET valid_until_ts = $4, expired_ts = $5, server_key = $6"
|
||||||
|
|
||||||
|
type serverKeyStatements struct {
|
||||||
|
db *sql.DB
|
||||||
|
bulkSelectServerKeysStmt *sql.Stmt
|
||||||
|
upsertServerKeysStmt *sql.Stmt
|
||||||
|
|
||||||
|
cache *lru.Cache // nameAndKeyID => gomatrixserverlib.PublicKeyLookupResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serverKeyStatements) prepare(db *sql.DB) (err error) {
|
||||||
|
s.db = db
|
||||||
|
s.cache, err = lru.New(64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = db.Exec(serverKeysSchema)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.bulkSelectServerKeysStmt, err = db.Prepare(bulkSelectServerKeysSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.upsertServerKeysStmt, err = db.Prepare(upsertServerKeysSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serverKeyStatements) bulkSelectServerKeys(
|
||||||
|
ctx context.Context,
|
||||||
|
requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp,
|
||||||
|
) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) {
|
||||||
|
var nameAndKeyIDs []string
|
||||||
|
for request := range requests {
|
||||||
|
nameAndKeyIDs = append(nameAndKeyIDs, nameAndKeyID(request))
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we can satisfy all of the requests from the cache, do so. TODO: Allow partial matches with merges.
|
||||||
|
cacheResults := map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult{}
|
||||||
|
for request := range requests {
|
||||||
|
r, ok := s.cache.Get(nameAndKeyID(request))
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cacheResult := r.(gomatrixserverlib.PublicKeyLookupResult)
|
||||||
|
cacheResults[request] = cacheResult
|
||||||
|
}
|
||||||
|
if len(cacheResults) == len(requests) {
|
||||||
|
util.GetLogger(ctx).Infof("KeyDB cache hit for %d keys", len(cacheResults))
|
||||||
|
return cacheResults, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
query := strings.Replace(bulkSelectServerKeysSQL, "($1)", common.QueryVariadic(len(nameAndKeyIDs)), 1)
|
||||||
|
|
||||||
|
iKeyIDs := make([]interface{}, len(nameAndKeyIDs))
|
||||||
|
for i, v := range nameAndKeyIDs {
|
||||||
|
iKeyIDs[i] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := s.db.QueryContext(ctx, query, iKeyIDs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer common.CloseAndLogIfError(ctx, rows, "bulkSelectServerKeys: rows.close() failed")
|
||||||
|
results := map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult{}
|
||||||
|
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 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r := gomatrixserverlib.PublicKeyLookupRequest{
|
||||||
|
ServerName: gomatrixserverlib.ServerName(serverName),
|
||||||
|
KeyID: gomatrixserverlib.KeyID(keyID),
|
||||||
|
}
|
||||||
|
vk := gomatrixserverlib.VerifyKey{}
|
||||||
|
err = vk.Key.Decode(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
results[r] = gomatrixserverlib.PublicKeyLookupResult{
|
||||||
|
VerifyKey: vk,
|
||||||
|
ValidUntilTS: gomatrixserverlib.Timestamp(validUntilTS),
|
||||||
|
ExpiredTS: gomatrixserverlib.Timestamp(expiredTS),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serverKeyStatements) upsertServerKeys(
|
||||||
|
ctx context.Context,
|
||||||
|
request gomatrixserverlib.PublicKeyLookupRequest,
|
||||||
|
key gomatrixserverlib.PublicKeyLookupResult,
|
||||||
|
) error {
|
||||||
|
s.cache.Add(nameAndKeyID(request), key)
|
||||||
|
_, err := s.upsertServerKeysStmt.ExecContext(
|
||||||
|
ctx,
|
||||||
|
string(request.ServerName),
|
||||||
|
string(request.KeyID),
|
||||||
|
nameAndKeyID(request),
|
||||||
|
key.ValidUntilTS,
|
||||||
|
key.ExpiredTS,
|
||||||
|
key.Key.Encode(),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func nameAndKeyID(request gomatrixserverlib.PublicKeyLookupRequest) string {
|
||||||
|
return string(request.ServerName) + "\x1F" + string(request.KeyID)
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue