diff options
author | ruben <code@rbn.im> | 2019-05-21 22:56:55 +0200 |
---|---|---|
committer | Brendan Abolivier <babolivier@matrix.org> | 2019-05-21 21:56:55 +0100 |
commit | 74827428bd3e11faab65f12204449c1b9469b0ae (patch) | |
tree | 0decafa542436a0667ed2d3e3cfd4df0f03de1e5 /federationapi | |
parent | 4d588f7008afe5600219ac0930c2eee2de5c447b (diff) |
use go module for dependencies (#594)
Diffstat (limited to 'federationapi')
-rw-r--r-- | federationapi/federationapi.go | 49 | ||||
-rw-r--r-- | federationapi/routing/backfill.go | 102 | ||||
-rw-r--r-- | federationapi/routing/devices.go | 53 | ||||
-rw-r--r-- | federationapi/routing/events.go | 84 | ||||
-rw-r--r-- | federationapi/routing/invite.go | 106 | ||||
-rw-r--r-- | federationapi/routing/join.go | 184 | ||||
-rw-r--r-- | federationapi/routing/keys.go | 68 | ||||
-rw-r--r-- | federationapi/routing/leave.go | 175 | ||||
-rw-r--r-- | federationapi/routing/missingevents.go | 80 | ||||
-rw-r--r-- | federationapi/routing/profile.go | 89 | ||||
-rw-r--r-- | federationapi/routing/query.go | 96 | ||||
-rw-r--r-- | federationapi/routing/routing.go | 230 | ||||
-rw-r--r-- | federationapi/routing/send.go | 219 | ||||
-rw-r--r-- | federationapi/routing/state.go | 149 | ||||
-rw-r--r-- | federationapi/routing/threepid.go | 355 | ||||
-rw-r--r-- | federationapi/routing/version.go | 35 | ||||
-rw-r--r-- | federationapi/types/types.go | 43 |
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} +} |