mirror of
https://github.com/hoernschen/dendrite.git
synced 2025-07-30 04:52:46 +00:00
use go module for dependencies (#594)
This commit is contained in:
parent
4d588f7008
commit
74827428bd
6109 changed files with 216 additions and 1114821 deletions
49
federationapi/federationapi.go
Normal file
49
federationapi/federationapi.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
// 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 federationapi
|
||||
|
||||
import (
|
||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
||||
"github.com/matrix-org/dendrite/common/basecomponent"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
|
||||
// TODO: Are we really wanting to pull in the producer from clientapi
|
||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||
"github.com/matrix-org/dendrite/federationapi/routing"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
)
|
||||
|
||||
// SetupFederationAPIComponent sets up and registers HTTP handlers for the
|
||||
// FederationAPI component.
|
||||
func SetupFederationAPIComponent(
|
||||
base *basecomponent.BaseDendrite,
|
||||
accountsDB *accounts.Database,
|
||||
deviceDB *devices.Database,
|
||||
federation *gomatrixserverlib.FederationClient,
|
||||
keyRing *gomatrixserverlib.KeyRing,
|
||||
aliasAPI roomserverAPI.RoomserverAliasAPI,
|
||||
inputAPI roomserverAPI.RoomserverInputAPI,
|
||||
queryAPI roomserverAPI.RoomserverQueryAPI,
|
||||
asAPI appserviceAPI.AppServiceQueryAPI,
|
||||
) {
|
||||
roomserverProducer := producers.NewRoomserverProducer(inputAPI)
|
||||
|
||||
routing.Setup(
|
||||
base.APIMux, *base.Cfg, queryAPI, aliasAPI, asAPI,
|
||||
roomserverProducer, *keyRing, federation, accountsDB, deviceDB,
|
||||
)
|
||||
}
|
102
federationapi/routing/backfill.go
Normal file
102
federationapi/routing/backfill.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
// Copyright 2018 New Vector 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 routing
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"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/federationapi/types"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
// Backfill implements the /backfill federation endpoint.
|
||||
// https://matrix.org/docs/spec/server_server/unstable.html#get-matrix-federation-v1-backfill-roomid
|
||||
func Backfill(
|
||||
httpReq *http.Request,
|
||||
request *gomatrixserverlib.FederationRequest,
|
||||
query api.RoomserverQueryAPI,
|
||||
roomID string,
|
||||
cfg config.Dendrite,
|
||||
) util.JSONResponse {
|
||||
var res api.QueryBackfillResponse
|
||||
var eIDs []string
|
||||
var limit string
|
||||
var exists bool
|
||||
var err error
|
||||
|
||||
// Check the room ID's format.
|
||||
if _, _, err = gomatrixserverlib.SplitID('!', roomID); err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.MissingArgument("Bad room ID: " + err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
// Check if all of the required parameters are there.
|
||||
eIDs, exists = httpReq.URL.Query()["v"]
|
||||
if !exists {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.MissingArgument("v is missing"),
|
||||
}
|
||||
}
|
||||
limit = httpReq.URL.Query().Get("limit")
|
||||
if len(limit) == 0 {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.MissingArgument("limit is missing"),
|
||||
}
|
||||
}
|
||||
|
||||
// Populate the request.
|
||||
req := api.QueryBackfillRequest{
|
||||
EarliestEventsIDs: eIDs,
|
||||
ServerName: request.Origin(),
|
||||
}
|
||||
if req.Limit, err = strconv.Atoi(limit); err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
// Query the roomserver.
|
||||
if err = query.QueryBackfill(httpReq.Context(), &req, &res); err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
// Filter any event that's not from the requested room out.
|
||||
evs := make([]gomatrixserverlib.Event, 0)
|
||||
|
||||
var ev gomatrixserverlib.Event
|
||||
for _, ev = range res.Events {
|
||||
if ev.RoomID() == roomID {
|
||||
evs = append(evs, ev)
|
||||
}
|
||||
}
|
||||
|
||||
txn := types.NewTransaction()
|
||||
txn.Origin = cfg.Matrix.ServerName
|
||||
txn.PDUs = evs
|
||||
|
||||
// Send the events to the client.
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: txn,
|
||||
}
|
||||
}
|
53
federationapi/routing/devices.go
Normal file
53
federationapi/routing/devices.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package routing
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"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/userutil"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
type userDevicesResponse struct {
|
||||
Devices []authtypes.Device `json:"devices"`
|
||||
}
|
||||
|
||||
// GetUserDevices for the given user id
|
||||
func GetUserDevices(
|
||||
req *http.Request,
|
||||
deviceDB *devices.Database,
|
||||
userID string,
|
||||
) util.JSONResponse {
|
||||
localpart, err := userutil.ParseUsernameParam(userID, nil)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.InvalidArgumentValue("Invalid user ID"),
|
||||
}
|
||||
}
|
||||
|
||||
devs, err := deviceDB.GetDevicesByLocalpart(req.Context(), localpart)
|
||||
if err != nil {
|
||||
return httputil.LogThenError(req, err)
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: userDevicesResponse{devs},
|
||||
}
|
||||
}
|
84
federationapi/routing/events.go
Normal file
84
federationapi/routing/events.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
// Copyright 2017 New Vector 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 routing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
// GetEvent returns the requested event
|
||||
func GetEvent(
|
||||
ctx context.Context,
|
||||
request *gomatrixserverlib.FederationRequest,
|
||||
query api.RoomserverQueryAPI,
|
||||
eventID string,
|
||||
) util.JSONResponse {
|
||||
event, err := getEvent(ctx, request, query, eventID)
|
||||
if err != nil {
|
||||
return *err
|
||||
}
|
||||
|
||||
return util.JSONResponse{Code: http.StatusOK, JSON: event}
|
||||
}
|
||||
|
||||
// getEvent returns the requested event,
|
||||
// otherwise it returns an error response which can be sent to the client.
|
||||
func getEvent(
|
||||
ctx context.Context,
|
||||
request *gomatrixserverlib.FederationRequest,
|
||||
query api.RoomserverQueryAPI,
|
||||
eventID string,
|
||||
) (*gomatrixserverlib.Event, *util.JSONResponse) {
|
||||
var authResponse api.QueryServerAllowedToSeeEventResponse
|
||||
err := query.QueryServerAllowedToSeeEvent(
|
||||
ctx,
|
||||
&api.QueryServerAllowedToSeeEventRequest{
|
||||
EventID: eventID,
|
||||
ServerName: request.Origin(),
|
||||
},
|
||||
&authResponse,
|
||||
)
|
||||
if err != nil {
|
||||
resErr := util.ErrorResponse(err)
|
||||
return nil, &resErr
|
||||
}
|
||||
|
||||
if !authResponse.AllowedToSeeEvent {
|
||||
resErr := util.MessageResponse(http.StatusForbidden, "server not allowed to see event")
|
||||
return nil, &resErr
|
||||
}
|
||||
|
||||
var eventsResponse api.QueryEventsByIDResponse
|
||||
err = query.QueryEventsByID(
|
||||
ctx,
|
||||
&api.QueryEventsByIDRequest{EventIDs: []string{eventID}},
|
||||
&eventsResponse,
|
||||
)
|
||||
if err != nil {
|
||||
resErr := util.ErrorResponse(err)
|
||||
return nil, &resErr
|
||||
}
|
||||
|
||||
if len(eventsResponse.Events) == 0 {
|
||||
return nil, &util.JSONResponse{Code: http.StatusNotFound, JSON: nil}
|
||||
}
|
||||
|
||||
return &eventsResponse.Events[0], nil
|
||||
}
|
106
federationapi/routing/invite.go
Normal file
106
federationapi/routing/invite.go
Normal file
|
@ -0,0 +1,106 @@
|
|||
// 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 routing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
// Invite implements /_matrix/federation/v1/invite/{roomID}/{eventID}
|
||||
func Invite(
|
||||
httpReq *http.Request,
|
||||
request *gomatrixserverlib.FederationRequest,
|
||||
roomID string,
|
||||
eventID string,
|
||||
cfg config.Dendrite,
|
||||
producer *producers.RoomserverProducer,
|
||||
keys gomatrixserverlib.KeyRing,
|
||||
) util.JSONResponse {
|
||||
|
||||
// Decode the event JSON from the request.
|
||||
var event gomatrixserverlib.Event
|
||||
if err := json.Unmarshal(request.Content(), &event); err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.NotJSON("The request body could not be decoded into valid JSON. " + err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the room ID is correct.
|
||||
if event.RoomID() != roomID {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("The room ID in the request path must match the room ID in the invite event JSON"),
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the event ID is correct.
|
||||
if event.EventID() != eventID {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("The event ID in the request path must match the event ID in the invite event JSON"),
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the event is from the server sending the request.
|
||||
if event.Origin() != request.Origin() {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("The invite must be sent by the server it originated on"),
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the event is signed by the server sending the request.
|
||||
verifyRequests := []gomatrixserverlib.VerifyJSONRequest{{
|
||||
ServerName: event.Origin(),
|
||||
Message: event.Redact().JSON(),
|
||||
AtTS: event.OriginServerTS(),
|
||||
}}
|
||||
verifyResults, err := keys.VerifyJSONs(httpReq.Context(), verifyRequests)
|
||||
if err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
if verifyResults[0].Error != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("The invite must be signed by the server it originated on"),
|
||||
}
|
||||
}
|
||||
|
||||
// Sign the event so that other servers will know that we have received the invite.
|
||||
signedEvent := event.Sign(
|
||||
string(cfg.Matrix.ServerName), cfg.Matrix.KeyID, cfg.Matrix.PrivateKey,
|
||||
)
|
||||
|
||||
// Add the invite event to the roomserver.
|
||||
if err = producer.SendInvite(httpReq.Context(), signedEvent); err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
// Return the signed event to the originating server, it should then tell
|
||||
// the other servers in the room that we have been invited.
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: gomatrixserverlib.RespInvite{Event: signedEvent},
|
||||
}
|
||||
}
|
184
federationapi/routing/join.go
Normal file
184
federationapi/routing/join.go
Normal file
|
@ -0,0 +1,184 @@
|
|||
// Copyright 2017 New Vector 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 routing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||
"github.com/matrix-org/dendrite/common"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
// MakeJoin implements the /make_join API
|
||||
func MakeJoin(
|
||||
httpReq *http.Request,
|
||||
request *gomatrixserverlib.FederationRequest,
|
||||
cfg config.Dendrite,
|
||||
query api.RoomserverQueryAPI,
|
||||
roomID, userID string,
|
||||
) util.JSONResponse {
|
||||
_, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("Invalid UserID"),
|
||||
}
|
||||
}
|
||||
if domain != request.Origin() {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("The join must be sent by the server of the user"),
|
||||
}
|
||||
}
|
||||
|
||||
// Try building an event for the server
|
||||
builder := gomatrixserverlib.EventBuilder{
|
||||
Sender: userID,
|
||||
RoomID: roomID,
|
||||
Type: "m.room.member",
|
||||
StateKey: &userID,
|
||||
}
|
||||
err = builder.SetContent(map[string]interface{}{"membership": "join"})
|
||||
if err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
var queryRes api.QueryLatestEventsAndStateResponse
|
||||
event, err := common.BuildEvent(httpReq.Context(), &builder, cfg, time.Now(), query, &queryRes)
|
||||
if err == common.ErrRoomNoExists {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound("Room does not exist"),
|
||||
}
|
||||
} else if err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
// Check that the join is allowed or not
|
||||
stateEvents := make([]*gomatrixserverlib.Event, len(queryRes.StateEvents))
|
||||
for i := range queryRes.StateEvents {
|
||||
stateEvents[i] = &queryRes.StateEvents[i]
|
||||
}
|
||||
provider := gomatrixserverlib.NewAuthEvents(stateEvents)
|
||||
if err = gomatrixserverlib.Allowed(*event, &provider); err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden(err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: map[string]interface{}{"event": builder},
|
||||
}
|
||||
}
|
||||
|
||||
// SendJoin implements the /send_join API
|
||||
func SendJoin(
|
||||
httpReq *http.Request,
|
||||
request *gomatrixserverlib.FederationRequest,
|
||||
cfg config.Dendrite,
|
||||
query api.RoomserverQueryAPI,
|
||||
producer *producers.RoomserverProducer,
|
||||
keys gomatrixserverlib.KeyRing,
|
||||
roomID, eventID string,
|
||||
) util.JSONResponse {
|
||||
var event gomatrixserverlib.Event
|
||||
if err := json.Unmarshal(request.Content(), &event); err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.NotJSON("The request body could not be decoded into valid JSON. " + err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the room ID is correct.
|
||||
if event.RoomID() != roomID {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("The room ID in the request path must match the room ID in the join event JSON"),
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the event ID is correct.
|
||||
if event.EventID() != eventID {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("The event ID in the request path must match the event ID in the join event JSON"),
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the event is from the server sending the request.
|
||||
if event.Origin() != request.Origin() {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("The join must be sent by the server it originated on"),
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the event is signed by the server sending the request.
|
||||
verifyRequests := []gomatrixserverlib.VerifyJSONRequest{{
|
||||
ServerName: event.Origin(),
|
||||
Message: event.Redact().JSON(),
|
||||
AtTS: event.OriginServerTS(),
|
||||
}}
|
||||
verifyResults, err := keys.VerifyJSONs(httpReq.Context(), verifyRequests)
|
||||
if err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
if verifyResults[0].Error != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("The join must be signed by the server it originated on"),
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the state and auth chain. We do this before we send the events
|
||||
// on, in case this fails.
|
||||
var stateAndAuthChainRepsonse api.QueryStateAndAuthChainResponse
|
||||
err = query.QueryStateAndAuthChain(httpReq.Context(), &api.QueryStateAndAuthChainRequest{
|
||||
PrevEventIDs: event.PrevEventIDs(),
|
||||
AuthEventIDs: event.AuthEventIDs(),
|
||||
RoomID: roomID,
|
||||
}, &stateAndAuthChainRepsonse)
|
||||
if err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
// Send the events to the room server.
|
||||
// We are responsible for notifying other servers that the user has joined
|
||||
// the room, so set SendAsServer to cfg.Matrix.ServerName
|
||||
_, err = producer.SendEvents(
|
||||
httpReq.Context(), []gomatrixserverlib.Event{event}, cfg.Matrix.ServerName, nil,
|
||||
)
|
||||
if err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: map[string]interface{}{
|
||||
"state": stateAndAuthChainRepsonse.StateEvents,
|
||||
"auth_chain": stateAndAuthChainRepsonse.AuthChainEvents,
|
||||
},
|
||||
}
|
||||
}
|
68
federationapi/routing/keys.go
Normal file
68
federationapi/routing/keys.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
// 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 routing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
// LocalKeys returns the local keys for the server.
|
||||
// See https://matrix.org/docs/spec/server_server/unstable.html#publishing-keys
|
||||
func LocalKeys(cfg config.Dendrite) util.JSONResponse {
|
||||
keys, err := localKeys(cfg, time.Now().Add(cfg.Matrix.KeyValidityPeriod))
|
||||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
return util.JSONResponse{Code: http.StatusOK, JSON: keys}
|
||||
}
|
||||
|
||||
func localKeys(cfg config.Dendrite, validUntil time.Time) (*gomatrixserverlib.ServerKeys, error) {
|
||||
var keys gomatrixserverlib.ServerKeys
|
||||
|
||||
keys.ServerName = cfg.Matrix.ServerName
|
||||
|
||||
publicKey := cfg.Matrix.PrivateKey.Public().(ed25519.PublicKey)
|
||||
|
||||
keys.VerifyKeys = map[gomatrixserverlib.KeyID]gomatrixserverlib.VerifyKey{
|
||||
cfg.Matrix.KeyID: {
|
||||
Key: gomatrixserverlib.Base64String(publicKey),
|
||||
},
|
||||
}
|
||||
|
||||
keys.TLSFingerprints = cfg.Matrix.TLSFingerPrints
|
||||
keys.OldVerifyKeys = map[gomatrixserverlib.KeyID]gomatrixserverlib.OldVerifyKey{}
|
||||
keys.ValidUntilTS = gomatrixserverlib.AsTimestamp(validUntil)
|
||||
|
||||
toSign, err := json.Marshal(keys.ServerKeyFields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys.Raw, err = gomatrixserverlib.SignJSON(
|
||||
string(cfg.Matrix.ServerName), cfg.Matrix.KeyID, cfg.Matrix.PrivateKey, toSign,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &keys, nil
|
||||
}
|
175
federationapi/routing/leave.go
Normal file
175
federationapi/routing/leave.go
Normal file
|
@ -0,0 +1,175 @@
|
|||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package routing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||
"github.com/matrix-org/dendrite/common"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
// MakeLeave implements the /make_leave API
|
||||
func MakeLeave(
|
||||
httpReq *http.Request,
|
||||
request *gomatrixserverlib.FederationRequest,
|
||||
cfg config.Dendrite,
|
||||
query api.RoomserverQueryAPI,
|
||||
roomID, userID string,
|
||||
) util.JSONResponse {
|
||||
_, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("Invalid UserID"),
|
||||
}
|
||||
}
|
||||
if domain != request.Origin() {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("The leave must be sent by the server of the user"),
|
||||
}
|
||||
}
|
||||
|
||||
// Try building an event for the server
|
||||
builder := gomatrixserverlib.EventBuilder{
|
||||
Sender: userID,
|
||||
RoomID: roomID,
|
||||
Type: "m.room.member",
|
||||
StateKey: &userID,
|
||||
}
|
||||
err = builder.SetContent(map[string]interface{}{"membership": "leave"})
|
||||
if err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
var queryRes api.QueryLatestEventsAndStateResponse
|
||||
event, err := common.BuildEvent(httpReq.Context(), &builder, cfg, time.Now(), query, &queryRes)
|
||||
if err == common.ErrRoomNoExists {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound("Room does not exist"),
|
||||
}
|
||||
} else if err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
// Check that the leave is allowed or not
|
||||
stateEvents := make([]*gomatrixserverlib.Event, len(queryRes.StateEvents))
|
||||
for i := range queryRes.StateEvents {
|
||||
stateEvents[i] = &queryRes.StateEvents[i]
|
||||
}
|
||||
provider := gomatrixserverlib.NewAuthEvents(stateEvents)
|
||||
if err = gomatrixserverlib.Allowed(*event, &provider); err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden(err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: map[string]interface{}{"event": builder},
|
||||
}
|
||||
}
|
||||
|
||||
// SendLeave implements the /send_leave API
|
||||
func SendLeave(
|
||||
httpReq *http.Request,
|
||||
request *gomatrixserverlib.FederationRequest,
|
||||
cfg config.Dendrite,
|
||||
producer *producers.RoomserverProducer,
|
||||
keys gomatrixserverlib.KeyRing,
|
||||
roomID, eventID string,
|
||||
) util.JSONResponse {
|
||||
var event gomatrixserverlib.Event
|
||||
if err := json.Unmarshal(request.Content(), &event); err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.NotJSON("The request body could not be decoded into valid JSON. " + err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the room ID is correct.
|
||||
if event.RoomID() != roomID {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("The room ID in the request path must match the room ID in the leave event JSON"),
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the event ID is correct.
|
||||
if event.EventID() != eventID {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("The event ID in the request path must match the event ID in the leave event JSON"),
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the event is from the server sending the request.
|
||||
if event.Origin() != request.Origin() {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("The leave must be sent by the server it originated on"),
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the event is signed by the server sending the request.
|
||||
verifyRequests := []gomatrixserverlib.VerifyJSONRequest{{
|
||||
ServerName: event.Origin(),
|
||||
Message: event.Redact().JSON(),
|
||||
AtTS: event.OriginServerTS(),
|
||||
}}
|
||||
verifyResults, err := keys.VerifyJSONs(httpReq.Context(), verifyRequests)
|
||||
if err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
if verifyResults[0].Error != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("The leave must be signed by the server it originated on"),
|
||||
}
|
||||
}
|
||||
|
||||
// check membership is set to leave
|
||||
mem, err := event.Membership()
|
||||
if err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
} else if mem != "leave" {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("The membership in the event content must be set to leave"),
|
||||
}
|
||||
}
|
||||
|
||||
// Send the events to the room server.
|
||||
// We are responsible for notifying other servers that the user has left
|
||||
// the room, so set SendAsServer to cfg.Matrix.ServerName
|
||||
_, err = producer.SendEvents(httpReq.Context(), []gomatrixserverlib.Event{event}, cfg.Matrix.ServerName, nil)
|
||||
if err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: struct{}{},
|
||||
}
|
||||
}
|
80
federationapi/routing/missingevents.go
Normal file
80
federationapi/routing/missingevents.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package routing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
type getMissingEventRequest struct {
|
||||
EarliestEvents []string `json:"earliest_events"`
|
||||
LatestEvents []string `json:"latest_events"`
|
||||
Limit int `json:"limit"`
|
||||
MinDepth int64 `json:"min_depth"`
|
||||
}
|
||||
|
||||
// GetMissingEvents returns missing events between earliest_events & latest_events.
|
||||
// Events are fetched from room DAG starting from latest_events until we reach earliest_events or the limit.
|
||||
func GetMissingEvents(
|
||||
httpReq *http.Request,
|
||||
request *gomatrixserverlib.FederationRequest,
|
||||
query api.RoomserverQueryAPI,
|
||||
roomID string,
|
||||
) util.JSONResponse {
|
||||
var gme getMissingEventRequest
|
||||
if err := json.Unmarshal(request.Content(), &gme); err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.NotJSON("The request body could not be decoded into valid JSON. " + err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
var eventsResponse api.QueryMissingEventsResponse
|
||||
if err := query.QueryMissingEvents(
|
||||
httpReq.Context(), &api.QueryMissingEventsRequest{
|
||||
EarliestEvents: gme.EarliestEvents,
|
||||
LatestEvents: gme.LatestEvents,
|
||||
Limit: gme.Limit,
|
||||
ServerName: request.Origin(),
|
||||
},
|
||||
&eventsResponse,
|
||||
); err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
eventsResponse.Events = filterEvents(eventsResponse.Events, gme.MinDepth, roomID)
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: eventsResponse,
|
||||
}
|
||||
}
|
||||
|
||||
// filterEvents returns only those events with matching roomID and having depth greater than minDepth
|
||||
func filterEvents(
|
||||
events []gomatrixserverlib.Event, minDepth int64, roomID string,
|
||||
) []gomatrixserverlib.Event {
|
||||
ref := events[:0]
|
||||
for _, ev := range events {
|
||||
if ev.Depth() >= minDepth && ev.RoomID() == roomID {
|
||||
ref = append(ref, ev)
|
||||
}
|
||||
}
|
||||
return ref
|
||||
}
|
89
federationapi/routing/profile.go
Normal file
89
federationapi/routing/profile.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
// Copyright 2017 New Vector 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 routing
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||
"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/common"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
// GetProfile implements GET /_matrix/federation/v1/query/profile
|
||||
func GetProfile(
|
||||
httpReq *http.Request,
|
||||
accountDB *accounts.Database,
|
||||
cfg config.Dendrite,
|
||||
asAPI appserviceAPI.AppServiceQueryAPI,
|
||||
) util.JSONResponse {
|
||||
userID, field := httpReq.FormValue("user_id"), httpReq.FormValue("field")
|
||||
|
||||
// httpReq.FormValue will return an empty string if value is not found
|
||||
if userID == "" {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.MissingArgument("The request body did not contain required argument 'user_id'."),
|
||||
}
|
||||
}
|
||||
|
||||
_, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||
if err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
if domain != cfg.Matrix.ServerName {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
profile, err := appserviceAPI.RetreiveUserProfile(httpReq.Context(), userID, asAPI, accountDB)
|
||||
if err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
var res interface{}
|
||||
code := http.StatusOK
|
||||
|
||||
if field != "" {
|
||||
switch field {
|
||||
case "displayname":
|
||||
res = common.DisplayName{
|
||||
DisplayName: profile.DisplayName,
|
||||
}
|
||||
case "avatar_url":
|
||||
res = common.AvatarURL{
|
||||
AvatarURL: profile.AvatarURL,
|
||||
}
|
||||
default:
|
||||
code = http.StatusBadRequest
|
||||
res = jsonerror.InvalidArgumentValue("The request body did not contain an allowed value of argument 'field'. Allowed values are either: 'avatar_url', 'displayname'.")
|
||||
}
|
||||
} else {
|
||||
res = common.ProfileResponse{
|
||||
AvatarURL: profile.AvatarURL,
|
||||
DisplayName: profile.DisplayName,
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: code,
|
||||
JSON: res,
|
||||
}
|
||||
}
|
96
federationapi/routing/query.go
Normal file
96
federationapi/routing/query.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
// Copyright 2017 New Vector 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 routing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/gomatrix"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
// RoomAliasToID converts the queried alias into a room ID and returns it
|
||||
func RoomAliasToID(
|
||||
httpReq *http.Request,
|
||||
federation *gomatrixserverlib.FederationClient,
|
||||
cfg config.Dendrite,
|
||||
aliasAPI roomserverAPI.RoomserverAliasAPI,
|
||||
) util.JSONResponse {
|
||||
roomAlias := httpReq.FormValue("room_alias")
|
||||
if roomAlias == "" {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("Must supply room alias parameter."),
|
||||
}
|
||||
}
|
||||
_, domain, err := gomatrixserverlib.SplitID('#', roomAlias)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("Room alias must be in the form '#localpart:domain'"),
|
||||
}
|
||||
}
|
||||
|
||||
var resp gomatrixserverlib.RespDirectory
|
||||
|
||||
if domain == cfg.Matrix.ServerName {
|
||||
queryReq := roomserverAPI.GetRoomIDForAliasRequest{Alias: roomAlias}
|
||||
var queryRes roomserverAPI.GetRoomIDForAliasResponse
|
||||
if err = aliasAPI.GetRoomIDForAlias(httpReq.Context(), &queryReq, &queryRes); err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
if queryRes.RoomID != "" {
|
||||
// TODO: List servers that are aware of this room alias
|
||||
resp = gomatrixserverlib.RespDirectory{
|
||||
RoomID: queryRes.RoomID,
|
||||
Servers: []gomatrixserverlib.ServerName{},
|
||||
}
|
||||
} else {
|
||||
// If no alias was found, return an error
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound(fmt.Sprintf("Room alias %s not found", roomAlias)),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resp, err = federation.LookupRoomAlias(httpReq.Context(), domain, roomAlias)
|
||||
if err != nil {
|
||||
switch x := err.(type) {
|
||||
case gomatrix.HTTPError:
|
||||
if x.Code == http.StatusNotFound {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound("Room alias not found"),
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: Return 502 if the remote server errored.
|
||||
// TODO: Return 504 if the remote server timed out.
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: resp,
|
||||
}
|
||||
}
|
230
federationapi/routing/routing.go
Normal file
230
federationapi/routing/routing.go
Normal file
|
@ -0,0 +1,230 @@
|
|||
// 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 routing
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||
"github.com/matrix-org/dendrite/common"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
const (
|
||||
pathPrefixV2Keys = "/_matrix/key/v2"
|
||||
pathPrefixV1Federation = "/_matrix/federation/v1"
|
||||
)
|
||||
|
||||
// Setup registers HTTP handlers with the given ServeMux.
|
||||
func Setup(
|
||||
apiMux *mux.Router,
|
||||
cfg config.Dendrite,
|
||||
query roomserverAPI.RoomserverQueryAPI,
|
||||
aliasAPI roomserverAPI.RoomserverAliasAPI,
|
||||
asAPI appserviceAPI.AppServiceQueryAPI,
|
||||
producer *producers.RoomserverProducer,
|
||||
keys gomatrixserverlib.KeyRing,
|
||||
federation *gomatrixserverlib.FederationClient,
|
||||
accountDB *accounts.Database,
|
||||
deviceDB *devices.Database,
|
||||
) {
|
||||
v2keysmux := apiMux.PathPrefix(pathPrefixV2Keys).Subrouter()
|
||||
v1fedmux := apiMux.PathPrefix(pathPrefixV1Federation).Subrouter()
|
||||
|
||||
localKeys := common.MakeExternalAPI("localkeys", func(req *http.Request) util.JSONResponse {
|
||||
return LocalKeys(cfg)
|
||||
})
|
||||
|
||||
// Ignore the {keyID} argument as we only have a single server key so we always
|
||||
// return that key.
|
||||
// Even if we had more than one server key, we would probably still ignore the
|
||||
// {keyID} argument and always return a response containing all of the keys.
|
||||
v2keysmux.Handle("/server/{keyID}", localKeys).Methods(http.MethodGet)
|
||||
v2keysmux.Handle("/server/", localKeys).Methods(http.MethodGet)
|
||||
|
||||
v1fedmux.Handle("/send/{txnID}/", common.MakeFedAPI(
|
||||
"federation_send", cfg.Matrix.ServerName, keys,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||
vars := mux.Vars(httpReq)
|
||||
return Send(
|
||||
httpReq, request, gomatrixserverlib.TransactionID(vars["txnID"]),
|
||||
cfg, query, producer, keys, federation,
|
||||
)
|
||||
},
|
||||
)).Methods(http.MethodPut, http.MethodOptions)
|
||||
|
||||
v1fedmux.Handle("/invite/{roomID}/{eventID}", common.MakeFedAPI(
|
||||
"federation_invite", cfg.Matrix.ServerName, keys,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||
vars := mux.Vars(httpReq)
|
||||
return Invite(
|
||||
httpReq, request, vars["roomID"], vars["eventID"],
|
||||
cfg, producer, keys,
|
||||
)
|
||||
},
|
||||
)).Methods(http.MethodPut, http.MethodOptions)
|
||||
|
||||
v1fedmux.Handle("/3pid/onbind", common.MakeExternalAPI("3pid_onbind",
|
||||
func(req *http.Request) util.JSONResponse {
|
||||
return CreateInvitesFrom3PIDInvites(req, query, asAPI, cfg, producer, federation, accountDB)
|
||||
},
|
||||
)).Methods(http.MethodPost, http.MethodOptions)
|
||||
|
||||
v1fedmux.Handle("/exchange_third_party_invite/{roomID}", common.MakeFedAPI(
|
||||
"exchange_third_party_invite", cfg.Matrix.ServerName, keys,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||
vars := mux.Vars(httpReq)
|
||||
return ExchangeThirdPartyInvite(
|
||||
httpReq, request, vars["roomID"], query, cfg, federation, producer,
|
||||
)
|
||||
},
|
||||
)).Methods(http.MethodPut, http.MethodOptions)
|
||||
|
||||
v1fedmux.Handle("/event/{eventID}", common.MakeFedAPI(
|
||||
"federation_get_event", cfg.Matrix.ServerName, keys,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||
vars := mux.Vars(httpReq)
|
||||
return GetEvent(
|
||||
httpReq.Context(), request, query, vars["eventID"],
|
||||
)
|
||||
},
|
||||
)).Methods(http.MethodGet)
|
||||
|
||||
v1fedmux.Handle("/state/{roomID}", common.MakeFedAPI(
|
||||
"federation_get_event_auth", cfg.Matrix.ServerName, keys,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||
vars := mux.Vars(httpReq)
|
||||
return GetState(
|
||||
httpReq.Context(), request, query, vars["roomID"],
|
||||
)
|
||||
},
|
||||
)).Methods(http.MethodGet)
|
||||
|
||||
v1fedmux.Handle("/state_ids/{roomID}", common.MakeFedAPI(
|
||||
"federation_get_event_auth", cfg.Matrix.ServerName, keys,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||
vars := mux.Vars(httpReq)
|
||||
return GetStateIDs(
|
||||
httpReq.Context(), request, query, vars["roomID"],
|
||||
)
|
||||
},
|
||||
)).Methods(http.MethodGet)
|
||||
|
||||
v1fedmux.Handle("/query/directory", common.MakeFedAPI(
|
||||
"federation_query_room_alias", cfg.Matrix.ServerName, keys,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||
return RoomAliasToID(
|
||||
httpReq, federation, cfg, aliasAPI,
|
||||
)
|
||||
},
|
||||
)).Methods(http.MethodGet)
|
||||
|
||||
v1fedmux.Handle("/query/profile", common.MakeFedAPI(
|
||||
"federation_query_profile", cfg.Matrix.ServerName, keys,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||
return GetProfile(
|
||||
httpReq, accountDB, cfg, asAPI,
|
||||
)
|
||||
},
|
||||
)).Methods(http.MethodGet)
|
||||
|
||||
v1fedmux.Handle("/user/devices/{userID}", common.MakeFedAPI(
|
||||
"federation_user_devices", cfg.Matrix.ServerName, keys,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||
vars := mux.Vars(httpReq)
|
||||
return GetUserDevices(
|
||||
httpReq, deviceDB, vars["userID"],
|
||||
)
|
||||
},
|
||||
)).Methods(http.MethodGet)
|
||||
|
||||
v1fedmux.Handle("/make_join/{roomID}/{userID}", common.MakeFedAPI(
|
||||
"federation_make_join", cfg.Matrix.ServerName, keys,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||
vars := mux.Vars(httpReq)
|
||||
roomID := vars["roomID"]
|
||||
userID := vars["userID"]
|
||||
return MakeJoin(
|
||||
httpReq, request, cfg, query, roomID, userID,
|
||||
)
|
||||
},
|
||||
)).Methods(http.MethodGet)
|
||||
|
||||
v1fedmux.Handle("/send_join/{roomID}/{userID}", common.MakeFedAPI(
|
||||
"federation_send_join", cfg.Matrix.ServerName, keys,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||
vars := mux.Vars(httpReq)
|
||||
roomID := vars["roomID"]
|
||||
userID := vars["userID"]
|
||||
return SendJoin(
|
||||
httpReq, request, cfg, query, producer, keys, roomID, userID,
|
||||
)
|
||||
},
|
||||
)).Methods(http.MethodPut)
|
||||
|
||||
v1fedmux.Handle("/make_leave/{roomID}/{userID}", common.MakeFedAPI(
|
||||
"federation_make_leave", cfg.Matrix.ServerName, keys,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||
vars := mux.Vars(httpReq)
|
||||
roomID := vars["roomID"]
|
||||
userID := vars["userID"]
|
||||
return MakeLeave(
|
||||
httpReq, request, cfg, query, roomID, userID,
|
||||
)
|
||||
},
|
||||
)).Methods(http.MethodGet)
|
||||
|
||||
v1fedmux.Handle("/send_leave/{roomID}/{userID}", common.MakeFedAPI(
|
||||
"federation_send_leave", cfg.Matrix.ServerName, keys,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||
vars := mux.Vars(httpReq)
|
||||
roomID := vars["roomID"]
|
||||
userID := vars["userID"]
|
||||
return SendLeave(
|
||||
httpReq, request, cfg, producer, keys, roomID, userID,
|
||||
)
|
||||
},
|
||||
)).Methods(http.MethodPut)
|
||||
|
||||
v1fedmux.Handle("/version", common.MakeExternalAPI(
|
||||
"federation_version",
|
||||
func(httpReq *http.Request) util.JSONResponse {
|
||||
return Version()
|
||||
},
|
||||
)).Methods(http.MethodGet)
|
||||
|
||||
v1fedmux.Handle("/get_missing_events/{roomID}", common.MakeFedAPI(
|
||||
"federation_get_missing_events", cfg.Matrix.ServerName, keys,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||
vars := mux.Vars(httpReq)
|
||||
return GetMissingEvents(httpReq, request, query, vars["roomID"])
|
||||
},
|
||||
)).Methods(http.MethodPost)
|
||||
|
||||
v1fedmux.Handle("/backfill/{roomID}/", common.MakeFedAPI(
|
||||
"federation_backfill", cfg.Matrix.ServerName, keys,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||
vars := mux.Vars(httpReq)
|
||||
return Backfill(httpReq, request, query, vars["roomID"], cfg)
|
||||
},
|
||||
)).Methods(http.MethodGet)
|
||||
}
|
219
federationapi/routing/send.go
Normal file
219
federationapi/routing/send.go
Normal file
|
@ -0,0 +1,219 @@
|
|||
// 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 routing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
// Send implements /_matrix/federation/v1/send/{txnID}
|
||||
func Send(
|
||||
httpReq *http.Request,
|
||||
request *gomatrixserverlib.FederationRequest,
|
||||
txnID gomatrixserverlib.TransactionID,
|
||||
cfg config.Dendrite,
|
||||
query api.RoomserverQueryAPI,
|
||||
producer *producers.RoomserverProducer,
|
||||
keys gomatrixserverlib.KeyRing,
|
||||
federation *gomatrixserverlib.FederationClient,
|
||||
) util.JSONResponse {
|
||||
|
||||
t := txnReq{
|
||||
context: httpReq.Context(),
|
||||
query: query,
|
||||
producer: producer,
|
||||
keys: keys,
|
||||
federation: federation,
|
||||
}
|
||||
if err := json.Unmarshal(request.Content(), &t); err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.NotJSON("The request body could not be decoded into valid JSON. " + err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
t.Origin = request.Origin()
|
||||
t.TransactionID = txnID
|
||||
t.Destination = cfg.Matrix.ServerName
|
||||
|
||||
resp, err := t.processTransaction()
|
||||
if err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: resp,
|
||||
}
|
||||
}
|
||||
|
||||
type txnReq struct {
|
||||
gomatrixserverlib.Transaction
|
||||
context context.Context
|
||||
query api.RoomserverQueryAPI
|
||||
producer *producers.RoomserverProducer
|
||||
keys gomatrixserverlib.KeyRing
|
||||
federation *gomatrixserverlib.FederationClient
|
||||
}
|
||||
|
||||
func (t *txnReq) processTransaction() (*gomatrixserverlib.RespSend, error) {
|
||||
// Check the event signatures
|
||||
if err := gomatrixserverlib.VerifyAllEventSignatures(t.context, t.PDUs, t.keys); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Process the events.
|
||||
results := map[string]gomatrixserverlib.PDUResult{}
|
||||
for _, e := range t.PDUs {
|
||||
err := t.processEvent(e)
|
||||
if err != nil {
|
||||
// If the error is due to the event itself being bad then we skip
|
||||
// it and move onto the next event. We report an error so that the
|
||||
// sender knows that we have skipped processing it.
|
||||
//
|
||||
// However if the event is due to a temporary failure in our server
|
||||
// such as a database being unavailable then we should bail, and
|
||||
// hope that the sender will retry when we are feeling better.
|
||||
//
|
||||
// It is uncertain what we should do if an event fails because
|
||||
// we failed to fetch more information from the sending server.
|
||||
// For example if a request to /state fails.
|
||||
// If we skip the event then we risk missing the event until we
|
||||
// receive another event referencing it.
|
||||
// If we bail and stop processing then we risk wedging incoming
|
||||
// transactions from that server forever.
|
||||
switch err.(type) {
|
||||
case unknownRoomError:
|
||||
case *gomatrixserverlib.NotAllowed:
|
||||
default:
|
||||
// Any other error should be the result of a temporary error in
|
||||
// our server so we should bail processing the transaction entirely.
|
||||
return nil, err
|
||||
}
|
||||
results[e.EventID()] = gomatrixserverlib.PDUResult{
|
||||
Error: err.Error(),
|
||||
}
|
||||
} else {
|
||||
results[e.EventID()] = gomatrixserverlib.PDUResult{}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Process the EDUs.
|
||||
|
||||
return &gomatrixserverlib.RespSend{PDUs: results}, nil
|
||||
}
|
||||
|
||||
type unknownRoomError struct {
|
||||
roomID string
|
||||
}
|
||||
|
||||
func (e unknownRoomError) Error() string { return fmt.Sprintf("unknown room %q", e.roomID) }
|
||||
|
||||
func (t *txnReq) processEvent(e gomatrixserverlib.Event) error {
|
||||
prevEventIDs := e.PrevEventIDs()
|
||||
|
||||
// Fetch the state needed to authenticate the event.
|
||||
needed := gomatrixserverlib.StateNeededForAuth([]gomatrixserverlib.Event{e})
|
||||
stateReq := api.QueryStateAfterEventsRequest{
|
||||
RoomID: e.RoomID(),
|
||||
PrevEventIDs: prevEventIDs,
|
||||
StateToFetch: needed.Tuples(),
|
||||
}
|
||||
var stateResp api.QueryStateAfterEventsResponse
|
||||
if err := t.query.QueryStateAfterEvents(t.context, &stateReq, &stateResp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !stateResp.RoomExists {
|
||||
// TODO: When synapse receives a message for a room it is not in it
|
||||
// asks the remote server for the state of the room so that it can
|
||||
// check if the remote server knows of a join "m.room.member" event
|
||||
// that this server is unaware of.
|
||||
// However generally speaking we should reject events for rooms we
|
||||
// aren't a member of.
|
||||
return unknownRoomError{e.RoomID()}
|
||||
}
|
||||
|
||||
if !stateResp.PrevEventsExist {
|
||||
return t.processEventWithMissingState(e)
|
||||
}
|
||||
|
||||
// Check that the event is allowed by the state at the event.
|
||||
if err := checkAllowedByState(e, stateResp.StateEvents); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Check that the roomserver has a copy of all of the auth_events.
|
||||
// TODO: Check that the event is allowed by its auth_events.
|
||||
|
||||
// pass the event to the roomserver
|
||||
_, err := t.producer.SendEvents(t.context, []gomatrixserverlib.Event{e}, api.DoNotSendToOtherServers, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func checkAllowedByState(e gomatrixserverlib.Event, stateEvents []gomatrixserverlib.Event) error {
|
||||
authUsingState := gomatrixserverlib.NewAuthEvents(nil)
|
||||
for i := range stateEvents {
|
||||
err := authUsingState.AddEvent(&stateEvents[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return gomatrixserverlib.Allowed(e, &authUsingState)
|
||||
}
|
||||
|
||||
func (t *txnReq) processEventWithMissingState(e gomatrixserverlib.Event) error {
|
||||
// We are missing the previous events for this events.
|
||||
// This means that there is a gap in our view of the history of the
|
||||
// room. There two ways that we can handle such a gap:
|
||||
// 1) We can fill in the gap using /get_missing_events
|
||||
// 2) We can leave the gap and request the state of the room at
|
||||
// this event from the remote server using either /state_ids
|
||||
// or /state.
|
||||
// Synapse will attempt to do 1 and if that fails or if the gap is
|
||||
// too large then it will attempt 2.
|
||||
// Synapse will use /state_ids if possible since usually the state
|
||||
// is largely unchanged and it is more efficient to fetch a list of
|
||||
// event ids and then use /event to fetch the individual events.
|
||||
// However not all version of synapse support /state_ids so you may
|
||||
// need to fallback to /state.
|
||||
// TODO: Attempt to fill in the gap using /get_missing_events
|
||||
// TODO: Attempt to fetch the state using /state_ids and /events
|
||||
state, err := t.federation.LookupState(t.context, t.Origin, e.RoomID(), e.EventID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Check that the returned state is valid.
|
||||
if err := state.Check(t.context, t.keys); err != nil {
|
||||
return err
|
||||
}
|
||||
// Check that the event is allowed by the state.
|
||||
if err := checkAllowedByState(e, state.StateEvents); err != nil {
|
||||
return err
|
||||
}
|
||||
// pass the event along with the state to the roomserver
|
||||
return t.producer.SendEventWithState(t.context, state, e)
|
||||
}
|
149
federationapi/routing/state.go
Normal file
149
federationapi/routing/state.go
Normal file
|
@ -0,0 +1,149 @@
|
|||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package routing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
// GetState returns state events & auth events for the roomID, eventID
|
||||
func GetState(
|
||||
ctx context.Context,
|
||||
request *gomatrixserverlib.FederationRequest,
|
||||
query api.RoomserverQueryAPI,
|
||||
roomID string,
|
||||
) util.JSONResponse {
|
||||
eventID, err := parseEventIDParam(request)
|
||||
if err != nil {
|
||||
return *err
|
||||
}
|
||||
|
||||
state, err := getState(ctx, request, query, roomID, eventID)
|
||||
if err != nil {
|
||||
return *err
|
||||
}
|
||||
|
||||
return util.JSONResponse{Code: http.StatusOK, JSON: state}
|
||||
}
|
||||
|
||||
// GetStateIDs returns state event IDs & auth event IDs for the roomID, eventID
|
||||
func GetStateIDs(
|
||||
ctx context.Context,
|
||||
request *gomatrixserverlib.FederationRequest,
|
||||
query api.RoomserverQueryAPI,
|
||||
roomID string,
|
||||
) util.JSONResponse {
|
||||
eventID, err := parseEventIDParam(request)
|
||||
if err != nil {
|
||||
return *err
|
||||
}
|
||||
|
||||
state, err := getState(ctx, request, query, roomID, eventID)
|
||||
if err != nil {
|
||||
return *err
|
||||
}
|
||||
|
||||
stateEventIDs := getIDsFromEvent(state.StateEvents)
|
||||
authEventIDs := getIDsFromEvent(state.AuthEvents)
|
||||
|
||||
return util.JSONResponse{Code: http.StatusOK, JSON: gomatrixserverlib.RespStateIDs{
|
||||
StateEventIDs: stateEventIDs,
|
||||
AuthEventIDs: authEventIDs,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func parseEventIDParam(
|
||||
request *gomatrixserverlib.FederationRequest,
|
||||
) (eventID string, resErr *util.JSONResponse) {
|
||||
URL, err := url.Parse(request.RequestURI())
|
||||
if err != nil {
|
||||
*resErr = util.ErrorResponse(err)
|
||||
return
|
||||
}
|
||||
|
||||
eventID = URL.Query().Get("event_id")
|
||||
if eventID == "" {
|
||||
resErr = &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.MissingArgument("event_id missing"),
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getState(
|
||||
ctx context.Context,
|
||||
request *gomatrixserverlib.FederationRequest,
|
||||
query api.RoomserverQueryAPI,
|
||||
roomID string,
|
||||
eventID string,
|
||||
) (*gomatrixserverlib.RespState, *util.JSONResponse) {
|
||||
event, resErr := getEvent(ctx, request, query, eventID)
|
||||
if resErr != nil {
|
||||
return nil, resErr
|
||||
}
|
||||
|
||||
prevEventIDs := getIDsFromEventRef(event.PrevEvents())
|
||||
authEventIDs := getIDsFromEventRef(event.AuthEvents())
|
||||
|
||||
var response api.QueryStateAndAuthChainResponse
|
||||
err := query.QueryStateAndAuthChain(
|
||||
ctx,
|
||||
&api.QueryStateAndAuthChainRequest{
|
||||
RoomID: roomID,
|
||||
PrevEventIDs: prevEventIDs,
|
||||
AuthEventIDs: authEventIDs,
|
||||
},
|
||||
&response,
|
||||
)
|
||||
if err != nil {
|
||||
resErr := util.ErrorResponse(err)
|
||||
return nil, &resErr
|
||||
}
|
||||
|
||||
if !response.RoomExists {
|
||||
return nil, &util.JSONResponse{Code: http.StatusNotFound, JSON: nil}
|
||||
}
|
||||
|
||||
return &gomatrixserverlib.RespState{
|
||||
StateEvents: response.StateEvents,
|
||||
AuthEvents: response.AuthChainEvents,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getIDsFromEventRef(events []gomatrixserverlib.EventReference) []string {
|
||||
IDs := make([]string, len(events))
|
||||
for i := range events {
|
||||
IDs[i] = events[i].EventID
|
||||
}
|
||||
|
||||
return IDs
|
||||
}
|
||||
|
||||
func getIDsFromEvent(events []gomatrixserverlib.Event) []string {
|
||||
IDs := make([]string, len(events))
|
||||
for i := range events {
|
||||
IDs[i] = events[i].EventID()
|
||||
}
|
||||
|
||||
return IDs
|
||||
}
|
355
federationapi/routing/threepid.go
Normal file
355
federationapi/routing/threepid.go
Normal file
|
@ -0,0 +1,355 @@
|
|||
// 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 routing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||
"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/producers"
|
||||
"github.com/matrix-org/dendrite/common"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type invite struct {
|
||||
MXID string `json:"mxid"`
|
||||
RoomID string `json:"room_id"`
|
||||
Sender string `json:"sender"`
|
||||
Token string `json:"token"`
|
||||
Signed common.TPInviteSigned `json:"signed"`
|
||||
}
|
||||
|
||||
type invites struct {
|
||||
Medium string `json:"medium"`
|
||||
Address string `json:"address"`
|
||||
MXID string `json:"mxid"`
|
||||
Invites []invite `json:"invites"`
|
||||
}
|
||||
|
||||
var (
|
||||
errNotLocalUser = errors.New("the user is not from this server")
|
||||
errNotInRoom = errors.New("the server isn't currently in the room")
|
||||
)
|
||||
|
||||
// CreateInvitesFrom3PIDInvites implements POST /_matrix/federation/v1/3pid/onbind
|
||||
func CreateInvitesFrom3PIDInvites(
|
||||
req *http.Request, queryAPI roomserverAPI.RoomserverQueryAPI,
|
||||
asAPI appserviceAPI.AppServiceQueryAPI, cfg config.Dendrite,
|
||||
producer *producers.RoomserverProducer, federation *gomatrixserverlib.FederationClient,
|
||||
accountDB *accounts.Database,
|
||||
) util.JSONResponse {
|
||||
var body invites
|
||||
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
|
||||
return *reqErr
|
||||
}
|
||||
|
||||
evs := []gomatrixserverlib.Event{}
|
||||
for _, inv := range body.Invites {
|
||||
event, err := createInviteFrom3PIDInvite(
|
||||
req.Context(), queryAPI, asAPI, cfg, inv, federation, accountDB,
|
||||
)
|
||||
if err != nil {
|
||||
return httputil.LogThenError(req, err)
|
||||
}
|
||||
if event != nil {
|
||||
evs = append(evs, *event)
|
||||
}
|
||||
}
|
||||
|
||||
// Send all the events
|
||||
if _, err := producer.SendEvents(req.Context(), evs, cfg.Matrix.ServerName, nil); err != nil {
|
||||
return httputil.LogThenError(req, err)
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
// ExchangeThirdPartyInvite implements PUT /_matrix/federation/v1/exchange_third_party_invite/{roomID}
|
||||
func ExchangeThirdPartyInvite(
|
||||
httpReq *http.Request,
|
||||
request *gomatrixserverlib.FederationRequest,
|
||||
roomID string,
|
||||
queryAPI roomserverAPI.RoomserverQueryAPI,
|
||||
cfg config.Dendrite,
|
||||
federation *gomatrixserverlib.FederationClient,
|
||||
producer *producers.RoomserverProducer,
|
||||
) util.JSONResponse {
|
||||
var builder gomatrixserverlib.EventBuilder
|
||||
if err := json.Unmarshal(request.Content(), &builder); err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.NotJSON("The request body could not be decoded into valid JSON. " + err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the room ID is correct.
|
||||
if builder.RoomID != roomID {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("The room ID in the request path must match the room ID in the invite event JSON"),
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the state key is correct.
|
||||
_, targetDomain, err := gomatrixserverlib.SplitID('@', *builder.StateKey)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("The event's state key isn't a Matrix user ID"),
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the target user is from the requesting homeserver.
|
||||
if targetDomain != request.Origin() {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("The event's state key doesn't have the same domain as the request's origin"),
|
||||
}
|
||||
}
|
||||
|
||||
// Auth and build the event from what the remote server sent us
|
||||
event, err := buildMembershipEvent(httpReq.Context(), &builder, queryAPI, cfg)
|
||||
if err == errNotInRoom {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound("Unknown room " + roomID),
|
||||
}
|
||||
} else if err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
// Ask the requesting server to sign the newly created event so we know it
|
||||
// acknowledged it
|
||||
signedEvent, err := federation.SendInvite(httpReq.Context(), request.Origin(), *event)
|
||||
if err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
// Send the event to the roomserver
|
||||
if _, err = producer.SendEvents(
|
||||
httpReq.Context(), []gomatrixserverlib.Event{signedEvent.Event}, cfg.Matrix.ServerName, nil,
|
||||
); err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
// createInviteFrom3PIDInvite processes an invite provided by the identity server
|
||||
// and creates a m.room.member event (with "invite" membership) from it.
|
||||
// Returns an error if there was a problem building the event or fetching the
|
||||
// necessary data to do so.
|
||||
func createInviteFrom3PIDInvite(
|
||||
ctx context.Context, queryAPI roomserverAPI.RoomserverQueryAPI,
|
||||
asAPI appserviceAPI.AppServiceQueryAPI, cfg config.Dendrite,
|
||||
inv invite, federation *gomatrixserverlib.FederationClient,
|
||||
accountDB *accounts.Database,
|
||||
) (*gomatrixserverlib.Event, error) {
|
||||
_, server, err := gomatrixserverlib.SplitID('@', inv.MXID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if server != cfg.Matrix.ServerName {
|
||||
return nil, errNotLocalUser
|
||||
}
|
||||
|
||||
// Build the event
|
||||
builder := &gomatrixserverlib.EventBuilder{
|
||||
Type: "m.room.member",
|
||||
Sender: inv.Sender,
|
||||
RoomID: inv.RoomID,
|
||||
StateKey: &inv.MXID,
|
||||
}
|
||||
|
||||
profile, err := appserviceAPI.RetreiveUserProfile(ctx, inv.MXID, asAPI, accountDB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content := common.MemberContent{
|
||||
AvatarURL: profile.AvatarURL,
|
||||
DisplayName: profile.DisplayName,
|
||||
Membership: "invite",
|
||||
ThirdPartyInvite: &common.TPInvite{
|
||||
Signed: inv.Signed,
|
||||
},
|
||||
}
|
||||
|
||||
if err = builder.SetContent(content); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
event, err := buildMembershipEvent(ctx, builder, queryAPI, cfg)
|
||||
if err == errNotInRoom {
|
||||
return nil, sendToRemoteServer(ctx, inv, federation, cfg, *builder)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return event, nil
|
||||
}
|
||||
|
||||
// buildMembershipEvent uses a builder for a m.room.member invite event derived
|
||||
// from a third-party invite to auth and build the said event. Returns the said
|
||||
// event.
|
||||
// Returns errNotInRoom if the server is not in the room the invite is for.
|
||||
// Returns an error if something failed during the process.
|
||||
func buildMembershipEvent(
|
||||
ctx context.Context,
|
||||
builder *gomatrixserverlib.EventBuilder, queryAPI roomserverAPI.RoomserverQueryAPI,
|
||||
cfg config.Dendrite,
|
||||
) (*gomatrixserverlib.Event, error) {
|
||||
eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(builder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ask the roomserver for information about this room
|
||||
queryReq := roomserverAPI.QueryLatestEventsAndStateRequest{
|
||||
RoomID: builder.RoomID,
|
||||
StateToFetch: eventsNeeded.Tuples(),
|
||||
}
|
||||
var queryRes roomserverAPI.QueryLatestEventsAndStateResponse
|
||||
if err = queryAPI.QueryLatestEventsAndState(ctx, &queryReq, &queryRes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !queryRes.RoomExists {
|
||||
// Use federation to auth the event
|
||||
return nil, errNotInRoom
|
||||
}
|
||||
|
||||
// Auth the event locally
|
||||
builder.Depth = queryRes.Depth
|
||||
builder.PrevEvents = queryRes.LatestEvents
|
||||
|
||||
authEvents := gomatrixserverlib.NewAuthEvents(nil)
|
||||
|
||||
for i := range queryRes.StateEvents {
|
||||
err = authEvents.AddEvent(&queryRes.StateEvents[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err = fillDisplayName(builder, authEvents); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
refs, err := eventsNeeded.AuthEventReferences(&authEvents)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
builder.AuthEvents = refs
|
||||
|
||||
eventID := fmt.Sprintf("$%s:%s", util.RandomString(16), cfg.Matrix.ServerName)
|
||||
event, err := builder.Build(
|
||||
eventID, time.Now(), cfg.Matrix.ServerName, cfg.Matrix.KeyID, cfg.Matrix.PrivateKey,
|
||||
)
|
||||
|
||||
return &event, err
|
||||
}
|
||||
|
||||
// sendToRemoteServer uses federation to send an invite provided by an identity
|
||||
// server to a remote server in case the current server isn't in the room the
|
||||
// invite is for.
|
||||
// Returns an error if it couldn't get the server names to reach or if all of
|
||||
// them responded with an error.
|
||||
func sendToRemoteServer(
|
||||
ctx context.Context, inv invite,
|
||||
federation *gomatrixserverlib.FederationClient, _ config.Dendrite,
|
||||
builder gomatrixserverlib.EventBuilder,
|
||||
) (err error) {
|
||||
remoteServers := make([]gomatrixserverlib.ServerName, 2)
|
||||
_, remoteServers[0], err = gomatrixserverlib.SplitID('@', inv.Sender)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Fallback to the room's server if the sender's domain is the same as
|
||||
// the current server's
|
||||
_, remoteServers[1], err = gomatrixserverlib.SplitID('!', inv.RoomID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, server := range remoteServers {
|
||||
err = federation.ExchangeThirdPartyInvite(ctx, server, builder)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
logrus.WithError(err).Warnf("failed to send 3PID invite via %s", server)
|
||||
}
|
||||
|
||||
return errors.New("failed to send 3PID invite via any server")
|
||||
}
|
||||
|
||||
// fillDisplayName looks in a list of auth events for a m.room.third_party_invite
|
||||
// event with the state key matching a given m.room.member event's content's token.
|
||||
// If such an event is found, fills the "display_name" attribute of the
|
||||
// "third_party_invite" structure in the m.room.member event with the display_name
|
||||
// from the m.room.third_party_invite event.
|
||||
// Returns an error if there was a problem parsing the m.room.third_party_invite
|
||||
// event's content or updating the m.room.member event's content.
|
||||
// Returns nil if no m.room.third_party_invite with a matching token could be
|
||||
// found. Returning an error isn't necessary in this case as the event will be
|
||||
// rejected by gomatrixserverlib.
|
||||
func fillDisplayName(
|
||||
builder *gomatrixserverlib.EventBuilder, authEvents gomatrixserverlib.AuthEvents,
|
||||
) error {
|
||||
var content common.MemberContent
|
||||
if err := json.Unmarshal(builder.Content, &content); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Look for the m.room.third_party_invite event
|
||||
thirdPartyInviteEvent, _ := authEvents.ThirdPartyInvite(content.ThirdPartyInvite.Signed.Token)
|
||||
|
||||
if thirdPartyInviteEvent == nil {
|
||||
// If the third party invite event doesn't exist then we can't use it to set the display name.
|
||||
return nil
|
||||
}
|
||||
|
||||
var thirdPartyInviteContent common.ThirdPartyInviteContent
|
||||
if err := json.Unmarshal(thirdPartyInviteEvent.Content(), &thirdPartyInviteContent); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Use the m.room.third_party_invite event to fill the "displayname" and
|
||||
// update the m.room.member event's content with it
|
||||
content.ThirdPartyInvite.DisplayName = thirdPartyInviteContent.DisplayName
|
||||
return builder.SetContent(content)
|
||||
}
|
35
federationapi/routing/version.go
Normal file
35
federationapi/routing/version.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Copyright 2017 New Vector 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 routing
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
type version struct {
|
||||
Server server `json:"server"`
|
||||
}
|
||||
|
||||
type server struct {
|
||||
Version string `json:"version"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Version returns the server version
|
||||
func Version() util.JSONResponse {
|
||||
return util.JSONResponse{Code: http.StatusOK, JSON: &version{server{"dev", "Dendrite"}}}
|
||||
}
|
43
federationapi/types/types.go
Normal file
43
federationapi/types/types.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
// Copyright 2018 New Vector 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 types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
)
|
||||
|
||||
// Transaction is the representation of a transaction from the federation API
|
||||
// See https://matrix.org/docs/spec/server_server/unstable.html for more info.
|
||||
type Transaction struct {
|
||||
// The server_name of the homeserver sending this transaction.
|
||||
Origin gomatrixserverlib.ServerName `json:"origin"`
|
||||
// POSIX timestamp in milliseconds on originating homeserver when this
|
||||
// transaction started.
|
||||
OriginServerTS int64 `json:"origin_server_ts"`
|
||||
// List of persistent updates to rooms.
|
||||
PDUs []gomatrixserverlib.Event `json:"pdus"`
|
||||
}
|
||||
|
||||
// NewTransaction sets the timestamp of a new transaction instance and then
|
||||
// returns the said instance.
|
||||
func NewTransaction() Transaction {
|
||||
// Retrieve the current timestamp in nanoseconds and make it a milliseconds
|
||||
// one.
|
||||
ts := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
|
||||
return Transaction{OriginServerTS: ts}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue