aboutsummaryrefslogtreecommitdiff
path: root/federationapi
diff options
context:
space:
mode:
authorruben <code@rbn.im>2019-05-21 22:56:55 +0200
committerBrendan Abolivier <babolivier@matrix.org>2019-05-21 21:56:55 +0100
commit74827428bd3e11faab65f12204449c1b9469b0ae (patch)
tree0decafa542436a0667ed2d3e3cfd4df0f03de1e5 /federationapi
parent4d588f7008afe5600219ac0930c2eee2de5c447b (diff)
use go module for dependencies (#594)
Diffstat (limited to 'federationapi')
-rw-r--r--federationapi/federationapi.go49
-rw-r--r--federationapi/routing/backfill.go102
-rw-r--r--federationapi/routing/devices.go53
-rw-r--r--federationapi/routing/events.go84
-rw-r--r--federationapi/routing/invite.go106
-rw-r--r--federationapi/routing/join.go184
-rw-r--r--federationapi/routing/keys.go68
-rw-r--r--federationapi/routing/leave.go175
-rw-r--r--federationapi/routing/missingevents.go80
-rw-r--r--federationapi/routing/profile.go89
-rw-r--r--federationapi/routing/query.go96
-rw-r--r--federationapi/routing/routing.go230
-rw-r--r--federationapi/routing/send.go219
-rw-r--r--federationapi/routing/state.go149
-rw-r--r--federationapi/routing/threepid.go355
-rw-r--r--federationapi/routing/version.go35
-rw-r--r--federationapi/types/types.go43
17 files changed, 2117 insertions, 0 deletions
diff --git a/federationapi/federationapi.go b/federationapi/federationapi.go
new file mode 100644
index 00000000..87402d97
--- /dev/null
+++ b/federationapi/federationapi.go
@@ -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,
+ )
+}
diff --git a/federationapi/routing/backfill.go b/federationapi/routing/backfill.go
new file mode 100644
index 00000000..d996db6a
--- /dev/null
+++ b/federationapi/routing/backfill.go
@@ -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,
+ }
+}
diff --git a/federationapi/routing/devices.go b/federationapi/routing/devices.go
new file mode 100644
index 00000000..ba8af7a9
--- /dev/null
+++ b/federationapi/routing/devices.go
@@ -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},
+ }
+}
diff --git a/federationapi/routing/events.go b/federationapi/routing/events.go
new file mode 100644
index 00000000..c4248022
--- /dev/null
+++ b/federationapi/routing/events.go
@@ -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
+}
diff --git a/federationapi/routing/invite.go b/federationapi/routing/invite.go
new file mode 100644
index 00000000..01a1bed2
--- /dev/null
+++ b/federationapi/routing/invite.go
@@ -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},
+ }
+}
diff --git a/federationapi/routing/join.go b/federationapi/routing/join.go
new file mode 100644
index 00000000..0b60408f
--- /dev/null
+++ b/federationapi/routing/join.go
@@ -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,
+ },
+ }
+}
diff --git a/federationapi/routing/keys.go b/federationapi/routing/keys.go
new file mode 100644
index 00000000..9c53d177
--- /dev/null
+++ b/federationapi/routing/keys.go
@@ -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
+}
diff --git a/federationapi/routing/leave.go b/federationapi/routing/leave.go
new file mode 100644
index 00000000..3c57d39d
--- /dev/null
+++ b/federationapi/routing/leave.go
@@ -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{}{},
+ }
+}
diff --git a/federationapi/routing/missingevents.go b/federationapi/routing/missingevents.go
new file mode 100644
index 00000000..0165b8a6
--- /dev/null
+++ b/federationapi/routing/missingevents.go
@@ -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
+}
diff --git a/federationapi/routing/profile.go b/federationapi/routing/profile.go
new file mode 100644
index 00000000..aa4fcdc4
--- /dev/null
+++ b/federationapi/routing/profile.go
@@ -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,
+ }
+}
diff --git a/federationapi/routing/query.go b/federationapi/routing/query.go
new file mode 100644
index 00000000..781b8ea4
--- /dev/null
+++ b/federationapi/routing/query.go
@@ -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,
+ }
+}
diff --git a/federationapi/routing/routing.go b/federationapi/routing/routing.go
new file mode 100644
index 00000000..035d54aa
--- /dev/null
+++ b/federationapi/routing/routing.go
@@ -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)
+}
diff --git a/federationapi/routing/send.go b/federationapi/routing/send.go
new file mode 100644
index 00000000..eab24874
--- /dev/null
+++ b/federationapi/routing/send.go
@@ -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)
+}
diff --git a/federationapi/routing/state.go b/federationapi/routing/state.go
new file mode 100644
index 00000000..130f8a4f
--- /dev/null
+++ b/federationapi/routing/state.go
@@ -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
+}
diff --git a/federationapi/routing/threepid.go b/federationapi/routing/threepid.go
new file mode 100644
index 00000000..27796067
--- /dev/null
+++ b/federationapi/routing/threepid.go
@@ -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)
+}
diff --git a/federationapi/routing/version.go b/federationapi/routing/version.go
new file mode 100644
index 00000000..14ecd21e
--- /dev/null
+++ b/federationapi/routing/version.go
@@ -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"}}}
+}
diff --git a/federationapi/types/types.go b/federationapi/types/types.go
new file mode 100644
index 00000000..24838d54
--- /dev/null
+++ b/federationapi/types/types.go
@@ -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}
+}