mirror of
https://github.com/matrix-org/dendrite.git
synced 2024-11-26 08:11:55 -06:00
Start implementing /join for room aliases for rooms the server is not in. (#115)
* Start implementing the join room API * Hacks to get join room working * Make the TLS fingerprint configurable * Fix the client API proxy to handle '#' correctly * Return a 200 OK response * Write the join event along with current state to the room server * Comment on the error handling * Fix typos * Fix tab * Add TODO for moving authEventIDs to gomatrixserverlib * Comment on why we ignore the key ID argument for local keys * Avoid 'preceeded' * Neaten the control flow * Neaten the control flow even more * Return pointers * Rename err to lastErr for the loop
This commit is contained in:
parent
445dce14ae
commit
84ad4ff9f6
|
@ -45,22 +45,61 @@ func NewRoomserverProducer(kafkaURIs []string, topic string) (*RoomserverProduce
|
||||||
func (c *RoomserverProducer) SendEvents(events []gomatrixserverlib.Event) error {
|
func (c *RoomserverProducer) SendEvents(events []gomatrixserverlib.Event) error {
|
||||||
eventIDs := make([]string, len(events))
|
eventIDs := make([]string, len(events))
|
||||||
ires := make([]api.InputRoomEvent, len(events))
|
ires := make([]api.InputRoomEvent, len(events))
|
||||||
for i := range events {
|
for i, event := range events {
|
||||||
var authEventIDs []string
|
ires[i] = api.InputRoomEvent{
|
||||||
for _, ref := range events[i].AuthEvents() {
|
|
||||||
authEventIDs = append(authEventIDs, ref.EventID)
|
|
||||||
}
|
|
||||||
ire := api.InputRoomEvent{
|
|
||||||
Kind: api.KindNew,
|
Kind: api.KindNew,
|
||||||
Event: events[i].JSON(),
|
Event: event.JSON(),
|
||||||
AuthEventIDs: authEventIDs,
|
AuthEventIDs: authEventIDs(event),
|
||||||
}
|
}
|
||||||
ires[i] = ire
|
eventIDs[i] = event.EventID()
|
||||||
eventIDs[i] = events[i].EventID()
|
|
||||||
}
|
}
|
||||||
return c.SendInputRoomEvents(ires, eventIDs)
|
return c.SendInputRoomEvents(ires, eventIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendEventWithState writes an event with KindNew to the roomserver input log
|
||||||
|
// with the state at the event as KindOutlier before it.
|
||||||
|
func (c *RoomserverProducer) SendEventWithState(state gomatrixserverlib.RespState, event gomatrixserverlib.Event) error {
|
||||||
|
outliers, err := state.Events()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
eventIDs := make([]string, len(outliers)+1)
|
||||||
|
ires := make([]api.InputRoomEvent, len(outliers)+1)
|
||||||
|
for i, outlier := range outliers {
|
||||||
|
ires[i] = api.InputRoomEvent{
|
||||||
|
Kind: api.KindOutlier,
|
||||||
|
Event: outlier.JSON(),
|
||||||
|
AuthEventIDs: authEventIDs(outlier),
|
||||||
|
}
|
||||||
|
eventIDs[i] = outlier.EventID()
|
||||||
|
}
|
||||||
|
|
||||||
|
stateEventIDs := make([]string, len(state.StateEvents))
|
||||||
|
for i := range state.StateEvents {
|
||||||
|
stateEventIDs[i] = state.StateEvents[i].EventID()
|
||||||
|
}
|
||||||
|
|
||||||
|
ires[len(outliers)] = api.InputRoomEvent{
|
||||||
|
Kind: api.KindNew,
|
||||||
|
Event: event.JSON(),
|
||||||
|
AuthEventIDs: authEventIDs(event),
|
||||||
|
HasState: true,
|
||||||
|
StateEventIDs: stateEventIDs,
|
||||||
|
}
|
||||||
|
eventIDs[len(outliers)] = event.EventID()
|
||||||
|
|
||||||
|
return c.SendInputRoomEvents(ires, eventIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Make this a method on gomatrixserverlib.Event
|
||||||
|
func authEventIDs(event gomatrixserverlib.Event) (ids []string) {
|
||||||
|
for _, ref := range event.AuthEvents() {
|
||||||
|
ids = append(ids, ref.EventID)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// SendInputRoomEvents writes the given input room events to the roomserver input log. The length of both
|
// SendInputRoomEvents writes the given input room events to the roomserver input log. The length of both
|
||||||
// arrays must match, and each element must correspond to the same event.
|
// arrays must match, and each element must correspond to the same event.
|
||||||
func (c *RoomserverProducer) SendInputRoomEvents(ires []api.InputRoomEvent, eventIDs []string) error {
|
func (c *RoomserverProducer) SendInputRoomEvents(ires []api.InputRoomEvent, eventIDs []string) error {
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/writers"
|
"github.com/matrix-org/dendrite/clientapi/writers"
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
@ -36,8 +37,14 @@ const pathPrefixR0 = "/_matrix/client/r0"
|
||||||
|
|
||||||
// Setup registers HTTP handlers with the given ServeMux. It also supplies the given http.Client
|
// Setup registers HTTP handlers with the given ServeMux. It also supplies the given http.Client
|
||||||
// to clients which need to make outbound HTTP requests.
|
// to clients which need to make outbound HTTP requests.
|
||||||
func Setup(servMux *http.ServeMux, httpClient *http.Client, cfg config.ClientAPI, producer *producers.RoomserverProducer,
|
func Setup(
|
||||||
queryAPI api.RoomserverQueryAPI, accountDB *accounts.Database, deviceDB *devices.Database) {
|
servMux *http.ServeMux, httpClient *http.Client, cfg config.ClientAPI,
|
||||||
|
producer *producers.RoomserverProducer, queryAPI api.RoomserverQueryAPI,
|
||||||
|
accountDB *accounts.Database,
|
||||||
|
deviceDB *devices.Database,
|
||||||
|
federation *gomatrixserverlib.FederationClient,
|
||||||
|
keyRing gomatrixserverlib.KeyRing,
|
||||||
|
) {
|
||||||
apiMux := mux.NewRouter()
|
apiMux := mux.NewRouter()
|
||||||
r0mux := apiMux.PathPrefix(pathPrefixR0).Subrouter()
|
r0mux := apiMux.PathPrefix(pathPrefixR0).Subrouter()
|
||||||
r0mux.Handle("/createRoom",
|
r0mux.Handle("/createRoom",
|
||||||
|
@ -45,6 +52,14 @@ func Setup(servMux *http.ServeMux, httpClient *http.Client, cfg config.ClientAPI
|
||||||
return writers.CreateRoom(req, device, cfg, producer)
|
return writers.CreateRoom(req, device, cfg, producer)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
r0mux.Handle("/join/{roomIDOrAlias}",
|
||||||
|
common.MakeAuthAPI("join", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
return writers.JoinRoomByIDOrAlias(
|
||||||
|
req, device, vars["roomIDOrAlias"], cfg, federation, producer, queryAPI, keyRing,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
r0mux.Handle("/rooms/{roomID}/send/{eventType}/{txnID}",
|
r0mux.Handle("/rooms/{roomID}/send/{eventType}/{txnID}",
|
||||||
common.MakeAuthAPI("send_message", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
common.MakeAuthAPI("send_message", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||||
vars := mux.Vars(req)
|
vars := mux.Vars(req)
|
||||||
|
|
263
src/github.com/matrix-org/dendrite/clientapi/writers/joinroom.go
Normal file
263
src/github.com/matrix-org/dendrite/clientapi/writers/joinroom.go
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
// 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 writers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/config"
|
||||||
|
"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/roomserver/api"
|
||||||
|
"github.com/matrix-org/gomatrix"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JoinRoomByIDOrAlias implements the "/join/{roomIDOrAlias}" API.
|
||||||
|
// https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-join-roomidoralias
|
||||||
|
func JoinRoomByIDOrAlias(
|
||||||
|
req *http.Request,
|
||||||
|
device *authtypes.Device,
|
||||||
|
roomIDOrAlias string,
|
||||||
|
cfg config.ClientAPI,
|
||||||
|
federation *gomatrixserverlib.FederationClient,
|
||||||
|
producer *producers.RoomserverProducer,
|
||||||
|
queryAPI api.RoomserverQueryAPI,
|
||||||
|
keyRing gomatrixserverlib.KeyRing,
|
||||||
|
) util.JSONResponse {
|
||||||
|
var content map[string]interface{} // must be a JSON object
|
||||||
|
if resErr := httputil.UnmarshalJSONRequest(req, &content); resErr != nil {
|
||||||
|
return *resErr
|
||||||
|
}
|
||||||
|
|
||||||
|
content["membership"] = "join"
|
||||||
|
|
||||||
|
r := joinRoomReq{req, content, device.UserID, cfg, federation, producer, queryAPI, keyRing}
|
||||||
|
|
||||||
|
if strings.HasPrefix(roomIDOrAlias, "!") {
|
||||||
|
return r.joinRoomByID()
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(roomIDOrAlias, "#") {
|
||||||
|
return r.joinRoomByAlias(roomIDOrAlias)
|
||||||
|
}
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 400,
|
||||||
|
JSON: jsonerror.BadJSON("Invalid first character for room ID or alias"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type joinRoomReq struct {
|
||||||
|
req *http.Request
|
||||||
|
content map[string]interface{}
|
||||||
|
userID string
|
||||||
|
cfg config.ClientAPI
|
||||||
|
federation *gomatrixserverlib.FederationClient
|
||||||
|
producer *producers.RoomserverProducer
|
||||||
|
queryAPI api.RoomserverQueryAPI
|
||||||
|
keyRing gomatrixserverlib.KeyRing
|
||||||
|
}
|
||||||
|
|
||||||
|
// joinRoomByID joins a room by room ID
|
||||||
|
func (r joinRoomReq) joinRoomByID() util.JSONResponse {
|
||||||
|
// TODO: Implement joining rooms by ID.
|
||||||
|
// A client should only join a room by room ID when it has an invite
|
||||||
|
// to the room. If the server is already in the room then we can
|
||||||
|
// lookup the invite and process the request as a normal state event.
|
||||||
|
// If the server is not in the room the we will need to look up the
|
||||||
|
// remote server the invite came from in order to request a join event
|
||||||
|
// from that server.
|
||||||
|
panic(fmt.Errorf("Joining rooms by ID is not implemented"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// joinRoomByAlias joins a room using a room alias.
|
||||||
|
func (r joinRoomReq) joinRoomByAlias(roomAlias string) util.JSONResponse {
|
||||||
|
domain, err := domainFromID(roomAlias)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 400,
|
||||||
|
JSON: jsonerror.BadJSON("Room alias must be in the form '#localpart:domain'"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if domain == r.cfg.ServerName {
|
||||||
|
// TODO: Implement joining local room aliases.
|
||||||
|
panic(fmt.Errorf("Joining local room aliases is not implemented"))
|
||||||
|
} else {
|
||||||
|
return r.joinRoomByRemoteAlias(domain, roomAlias)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r joinRoomReq) joinRoomByRemoteAlias(
|
||||||
|
domain gomatrixserverlib.ServerName, roomAlias string,
|
||||||
|
) util.JSONResponse {
|
||||||
|
resp, err := r.federation.LookupRoomAlias(domain, roomAlias)
|
||||||
|
if err != nil {
|
||||||
|
switch x := err.(type) {
|
||||||
|
case gomatrix.HTTPError:
|
||||||
|
if x.Code == 404 {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 404,
|
||||||
|
JSON: jsonerror.NotFound("Room alias not found"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return httputil.LogThenError(r.req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.joinRoomUsingServers(resp.RoomID, resp.Servers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r joinRoomReq) writeToBuilder(eb *gomatrixserverlib.EventBuilder, roomID string) {
|
||||||
|
eb.Type = "m.room.member"
|
||||||
|
eb.SetContent(r.content) // TODO: Set avatar_url / displayname
|
||||||
|
eb.SetUnsigned(struct{}{})
|
||||||
|
eb.Sender = r.userID
|
||||||
|
eb.StateKey = &r.userID
|
||||||
|
eb.RoomID = roomID
|
||||||
|
eb.Redacts = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r joinRoomReq) joinRoomUsingServers(
|
||||||
|
roomID string, servers []gomatrixserverlib.ServerName,
|
||||||
|
) util.JSONResponse {
|
||||||
|
var eb gomatrixserverlib.EventBuilder
|
||||||
|
r.writeToBuilder(&eb, roomID)
|
||||||
|
|
||||||
|
needed, err := gomatrixserverlib.StateNeededForEventBuilder(&eb)
|
||||||
|
if err != nil {
|
||||||
|
return httputil.LogThenError(r.req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask the roomserver for information about this room
|
||||||
|
queryReq := api.QueryLatestEventsAndStateRequest{
|
||||||
|
RoomID: roomID,
|
||||||
|
StateToFetch: needed.Tuples(),
|
||||||
|
}
|
||||||
|
var queryRes api.QueryLatestEventsAndStateResponse
|
||||||
|
if queryErr := r.queryAPI.QueryLatestEventsAndState(&queryReq, &queryRes); queryErr != nil {
|
||||||
|
return httputil.LogThenError(r.req, queryErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if queryRes.RoomExists {
|
||||||
|
// TODO: Implement joining rooms that already the server is already in.
|
||||||
|
// This should just fall through to the usual event sending code.
|
||||||
|
panic(fmt.Errorf("Joining rooms that the server already in is not implemented"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 404,
|
||||||
|
JSON: jsonerror.NotFound("No candidate servers found for room"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastErr error
|
||||||
|
for _, server := range servers {
|
||||||
|
var response *util.JSONResponse
|
||||||
|
response, lastErr = r.joinRoomUsingServer(roomID, server)
|
||||||
|
if lastErr != nil {
|
||||||
|
// There was a problem talking to one of the servers.
|
||||||
|
util.GetLogger(r.req.Context()).WithError(lastErr).WithField("server", server).Warn("Failed to join room using server")
|
||||||
|
// Try the next server.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return *response
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every server we tried to join through resulted in an error.
|
||||||
|
// We return the error from the last server.
|
||||||
|
|
||||||
|
// TODO: Generate the correct HTTP status code for all different
|
||||||
|
// kinds of errors that could have happened.
|
||||||
|
// The possible errors include:
|
||||||
|
// 1) We can't connect to the remote servers.
|
||||||
|
// 2) None of the servers we could connect to think we are allowed
|
||||||
|
// to join the room.
|
||||||
|
// 3) The remote server returned something invalid.
|
||||||
|
// 4) We couldn't fetch the public keys needed to verify the
|
||||||
|
// signatures on the state events.
|
||||||
|
// 5) ...
|
||||||
|
return httputil.LogThenError(r.req, lastErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// joinRoomUsingServer tries to join a remote room using a given matrix server.
|
||||||
|
// If there was a failure communicating with the server or the response from the
|
||||||
|
// server was invalid this returns an error.
|
||||||
|
// Otherwise this returns a JSONResponse.
|
||||||
|
func (r joinRoomReq) joinRoomUsingServer(roomID string, server gomatrixserverlib.ServerName) (*util.JSONResponse, error) {
|
||||||
|
respMakeJoin, err := r.federation.MakeJoin(server, roomID, r.userID)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Check if the user was not allowed to join the room.
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set all the fields to be what they should be, this should be a no-op
|
||||||
|
// but it's possible that the remote server returned us something "odd"
|
||||||
|
r.writeToBuilder(&respMakeJoin.JoinEvent, roomID)
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
eventID := fmt.Sprintf("$%s:%s", util.RandomString(16), r.cfg.ServerName)
|
||||||
|
event, err := respMakeJoin.JoinEvent.Build(
|
||||||
|
eventID, now, r.cfg.ServerName, r.cfg.KeyID, r.cfg.PrivateKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
res := httputil.LogThenError(r.req, err)
|
||||||
|
return &res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
respSendJoin, err := r.federation.SendJoin(server, event)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = respSendJoin.Check(r.keyRing, event); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = r.producer.SendEventWithState(
|
||||||
|
gomatrixserverlib.RespState(respSendJoin), event,
|
||||||
|
); err != nil {
|
||||||
|
res := httputil.LogThenError(r.req, err)
|
||||||
|
return &res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &util.JSONResponse{
|
||||||
|
Code: 200,
|
||||||
|
// TODO: Put the response struct somewhere common.
|
||||||
|
JSON: struct {
|
||||||
|
RoomID string `json:"room_id"`
|
||||||
|
}{roomID},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// domainFromID returns everything after the first ":" character to extract
|
||||||
|
// the domain part of a matrix ID.
|
||||||
|
// TODO: duplicated from gomatrixserverlib.
|
||||||
|
func domainFromID(id string) (gomatrixserverlib.ServerName, error) {
|
||||||
|
// IDs have the format: SIGIL LOCALPART ":" DOMAIN
|
||||||
|
// Split on the first ":" character since the domain can contain ":"
|
||||||
|
// characters.
|
||||||
|
parts := strings.SplitN(id, ":", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
// The ID must have a ":" character.
|
||||||
|
return "", fmt.Errorf("invalid ID: %q", id)
|
||||||
|
}
|
||||||
|
// Return everything after the first ":" character.
|
||||||
|
return gomatrixserverlib.ServerName(parts[1]), nil
|
||||||
|
}
|
|
@ -76,7 +76,11 @@ func makeProxy(targetURL string) (*httputil.ReverseProxy, error) {
|
||||||
"url": targetURL,
|
"url": targetURL,
|
||||||
"method": req.Method,
|
"method": req.Method,
|
||||||
}).Print("proxying request")
|
}).Print("proxying request")
|
||||||
newURL, err := url.Parse(targetURL + path)
|
newURL, err := url.Parse(targetURL)
|
||||||
|
// Set the path separately as we need to preserve '#' characters
|
||||||
|
// that would otherwise be interpreted as being the start of a URL
|
||||||
|
// fragment.
|
||||||
|
newURL.Path += path
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// We already checked that we can parse the URL
|
// We already checked that we can parse the URL
|
||||||
// So this shouldn't ever get hit.
|
// So this shouldn't ever get hit.
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/routing"
|
"github.com/matrix-org/dendrite/clientapi/routing"
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
|
@ -81,6 +82,8 @@ func main() {
|
||||||
log.Panicf("Failed to setup kafka producers(%s): %s", cfg.KafkaProducerURIs, err)
|
log.Panicf("Failed to setup kafka producers(%s): %s", cfg.KafkaProducerURIs, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
federation := gomatrixserverlib.NewFederationClient(cfg.ServerName, cfg.KeyID, cfg.PrivateKey)
|
||||||
|
|
||||||
queryAPI := api.NewRoomserverQueryAPIHTTP(cfg.RoomserverURL, nil)
|
queryAPI := api.NewRoomserverQueryAPIHTTP(cfg.RoomserverURL, nil)
|
||||||
accountDB, err := accounts.NewDatabase(accountDataSource, serverName)
|
accountDB, err := accounts.NewDatabase(accountDataSource, serverName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -91,6 +94,32 @@ func main() {
|
||||||
log.Panicf("Failed to setup device database(%s): %s", accountDataSource, err.Error())
|
log.Panicf("Failed to setup device database(%s): %s", accountDataSource, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
routing.Setup(http.DefaultServeMux, http.DefaultClient, cfg, roomserverProducer, queryAPI, accountDB, deviceDB)
|
keyRing := gomatrixserverlib.KeyRing{
|
||||||
|
KeyFetchers: []gomatrixserverlib.KeyFetcher{
|
||||||
|
// TODO: Use perspective key fetchers for production.
|
||||||
|
&gomatrixserverlib.DirectKeyFetcher{federation.Client},
|
||||||
|
},
|
||||||
|
KeyDatabase: &dummyKeyDatabase{},
|
||||||
|
}
|
||||||
|
|
||||||
|
routing.Setup(
|
||||||
|
http.DefaultServeMux, http.DefaultClient, cfg, roomserverProducer,
|
||||||
|
queryAPI, accountDB, deviceDB, federation, keyRing,
|
||||||
|
)
|
||||||
log.Fatal(http.ListenAndServe(bindAddr, nil))
|
log.Fatal(http.ListenAndServe(bindAddr, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Implement a proper key database.
|
||||||
|
type dummyKeyDatabase struct{}
|
||||||
|
|
||||||
|
func (d *dummyKeyDatabase) FetchKeys(
|
||||||
|
requests map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.Timestamp,
|
||||||
|
) (map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.ServerKeys, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dummyKeyDatabase) StoreKeys(
|
||||||
|
map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.ServerKeys,
|
||||||
|
) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
@ -32,6 +33,14 @@ var (
|
||||||
logDir = os.Getenv("LOG_DIR")
|
logDir = os.Getenv("LOG_DIR")
|
||||||
serverName = gomatrixserverlib.ServerName(os.Getenv("SERVER_NAME"))
|
serverName = gomatrixserverlib.ServerName(os.Getenv("SERVER_NAME"))
|
||||||
serverKey = os.Getenv("SERVER_KEY")
|
serverKey = os.Getenv("SERVER_KEY")
|
||||||
|
// Base64 encoded SHA256 TLS fingerprint of the X509 certificate used by
|
||||||
|
// the public federation listener for this server.
|
||||||
|
// Can be generated from a PEM certificate called "server.crt" using:
|
||||||
|
//
|
||||||
|
// openssl x509 -noout -fingerprint -sha256 -inform pem -in server.crt |\
|
||||||
|
// python -c 'print raw_input()[19:].replace(":","").decode("hex").encode("base64").rstrip("=\n")'
|
||||||
|
//
|
||||||
|
tlsFingerprint = os.Getenv("TLS_FINGERPRINT")
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -44,6 +53,10 @@ func main() {
|
||||||
serverName = "localhost"
|
serverName = "localhost"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tlsFingerprint == "" {
|
||||||
|
log.Panic("No TLS_FINGERPRINT environment variable found.")
|
||||||
|
}
|
||||||
|
|
||||||
cfg := config.FederationAPI{
|
cfg := config.FederationAPI{
|
||||||
ServerName: serverName,
|
ServerName: serverName,
|
||||||
// TODO: make the validity period configurable.
|
// TODO: make the validity period configurable.
|
||||||
|
@ -56,6 +69,12 @@ func main() {
|
||||||
log.Panicf("Failed to load private key: %s", err)
|
log.Panicf("Failed to load private key: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fingerprintSHA256 []byte
|
||||||
|
if fingerprintSHA256, err = base64.RawStdEncoding.DecodeString(tlsFingerprint); err != nil {
|
||||||
|
log.Panicf("Failed to load TLS fingerprint: %s", err)
|
||||||
|
}
|
||||||
|
cfg.TLSFingerPrints = []gomatrixserverlib.TLSFingerprint{{fingerprintSHA256}}
|
||||||
|
|
||||||
routing.Setup(http.DefaultServeMux, cfg)
|
routing.Setup(http.DefaultServeMux, cfg)
|
||||||
log.Fatal(http.ListenAndServe(bindAddr, nil))
|
log.Fatal(http.ListenAndServe(bindAddr, nil))
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
pathPrefixV2Keys = "/_matrix/keys/v2"
|
pathPrefixV2Keys = "/_matrix/key/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Setup registers HTTP handlers with the given ServeMux.
|
// Setup registers HTTP handlers with the given ServeMux.
|
||||||
|
@ -32,11 +32,16 @@ func Setup(servMux *http.ServeMux, cfg config.FederationAPI) {
|
||||||
apiMux := mux.NewRouter()
|
apiMux := mux.NewRouter()
|
||||||
v2keysmux := apiMux.PathPrefix(pathPrefixV2Keys).Subrouter()
|
v2keysmux := apiMux.PathPrefix(pathPrefixV2Keys).Subrouter()
|
||||||
|
|
||||||
v2keysmux.Handle("/server/",
|
localKeys := makeAPI("localkeys", func(req *http.Request) util.JSONResponse {
|
||||||
makeAPI("localkeys", func(req *http.Request) util.JSONResponse {
|
return readers.LocalKeys(req, cfg)
|
||||||
return readers.LocalKeys(req, 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)
|
||||||
|
v2keysmux.Handle("/server/", localKeys)
|
||||||
|
|
||||||
servMux.Handle("/metrics", prometheus.Handler())
|
servMux.Handle("/metrics", prometheus.Handler())
|
||||||
servMux.Handle("/api/", http.StripPrefix("/api", apiMux))
|
servMux.Handle("/api/", http.StripPrefix("/api", apiMux))
|
||||||
|
|
2
vendor/manifest
vendored
2
vendor/manifest
vendored
|
@ -98,7 +98,7 @@
|
||||||
{
|
{
|
||||||
"importpath": "github.com/matrix-org/gomatrixserverlib",
|
"importpath": "github.com/matrix-org/gomatrixserverlib",
|
||||||
"repository": "https://github.com/matrix-org/gomatrixserverlib",
|
"repository": "https://github.com/matrix-org/gomatrixserverlib",
|
||||||
"revision": "9cefcd6c3a00bff51e719a33e19a16edf52cdd6f",
|
"revision": "c396ef3cc1e546729f7052f1f48e345cc59269f4",
|
||||||
"branch": "master"
|
"branch": "master"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -386,7 +386,7 @@ func (e Event) CheckFields() error {
|
||||||
// are allowed to have a different sender because they have the same
|
// are allowed to have a different sender because they have the same
|
||||||
// sender as the "m.room.third_party_invite" event they derived from.
|
// sender as the "m.room.third_party_invite" event they derived from.
|
||||||
// https://github.com/matrix-org/synapse/blob/v0.21.0/synapse/event_auth.py#L58-L64
|
// https://github.com/matrix-org/synapse/blob/v0.21.0/synapse/event_auth.py#L58-L64
|
||||||
if e.fields.Type != "m.room.member" {
|
if e.fields.Type != MRoomMember {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"gomatrixserverlib: sender domain doesn't match origin: %q != %q",
|
"gomatrixserverlib: sender domain doesn't match origin: %q != %q",
|
||||||
eventDomain, e.fields.Origin,
|
eventDomain, e.fields.Origin,
|
||||||
|
|
|
@ -28,6 +28,22 @@ const (
|
||||||
leave = "leave"
|
leave = "leave"
|
||||||
invite = "invite"
|
invite = "invite"
|
||||||
public = "public"
|
public = "public"
|
||||||
|
// MRoomCreate https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-create
|
||||||
|
MRoomCreate = "m.room.create"
|
||||||
|
// MRoomJoinRules https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-join-rules
|
||||||
|
MRoomJoinRules = "m.room.join_rules"
|
||||||
|
// MRoomPowerLevels https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-power-levels
|
||||||
|
MRoomPowerLevels = "m.room.power_levels"
|
||||||
|
// MRoomMember https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-member
|
||||||
|
MRoomMember = "m.room.member"
|
||||||
|
// MRoomThirdPartyInvite https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-third-party-invite
|
||||||
|
MRoomThirdPartyInvite = "m.room.third_party_invite"
|
||||||
|
// MRoomAliases https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-aliases
|
||||||
|
MRoomAliases = "m.room.aliases"
|
||||||
|
// MRoomHistoryVisibility https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-history-visibility
|
||||||
|
MRoomHistoryVisibility = "m.room.history_visibility"
|
||||||
|
// MRoomRedaction https://matrix.org/docs/spec/client_server/r0.2.0.html#id21
|
||||||
|
MRoomRedaction = "m.room.redaction"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StateNeeded lists the event types and state_keys needed to authenticate an event.
|
// StateNeeded lists the event types and state_keys needed to authenticate an event.
|
||||||
|
@ -47,19 +63,19 @@ type StateNeeded struct {
|
||||||
// Tuples returns the needed state key tuples for performing auth on an event.
|
// Tuples returns the needed state key tuples for performing auth on an event.
|
||||||
func (s StateNeeded) Tuples() (res []StateKeyTuple) {
|
func (s StateNeeded) Tuples() (res []StateKeyTuple) {
|
||||||
if s.Create {
|
if s.Create {
|
||||||
res = append(res, StateKeyTuple{"m.room.create", ""})
|
res = append(res, StateKeyTuple{MRoomCreate, ""})
|
||||||
}
|
}
|
||||||
if s.JoinRules {
|
if s.JoinRules {
|
||||||
res = append(res, StateKeyTuple{"m.room.join_rules", ""})
|
res = append(res, StateKeyTuple{MRoomJoinRules, ""})
|
||||||
}
|
}
|
||||||
if s.PowerLevels {
|
if s.PowerLevels {
|
||||||
res = append(res, StateKeyTuple{"m.room.power_levels", ""})
|
res = append(res, StateKeyTuple{MRoomPowerLevels, ""})
|
||||||
}
|
}
|
||||||
for _, userID := range s.Member {
|
for _, userID := range s.Member {
|
||||||
res = append(res, StateKeyTuple{"m.room.member", userID})
|
res = append(res, StateKeyTuple{MRoomMember, userID})
|
||||||
}
|
}
|
||||||
for _, token := range s.ThirdPartyInvite {
|
for _, token := range s.ThirdPartyInvite {
|
||||||
res = append(res, StateKeyTuple{"m.room.third_party_invite", token})
|
res = append(res, StateKeyTuple{MRoomThirdPartyInvite, token})
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -114,7 +130,7 @@ func (s StateNeeded) AuthEventReferences(provider AuthEventProvider) (refs []Eve
|
||||||
func StateNeededForEventBuilder(builder *EventBuilder) (result StateNeeded, err error) {
|
func StateNeededForEventBuilder(builder *EventBuilder) (result StateNeeded, err error) {
|
||||||
// Extract the 'content' object from the event if it is m.room.member as we need to know 'membership'
|
// Extract the 'content' object from the event if it is m.room.member as we need to know 'membership'
|
||||||
var content *memberContent
|
var content *memberContent
|
||||||
if builder.Type == "m.room.member" {
|
if builder.Type == MRoomMember {
|
||||||
if err = json.Unmarshal(builder.Content, &content); err != nil {
|
if err = json.Unmarshal(builder.Content, &content); err != nil {
|
||||||
err = errorf("unparsable member event content: %s", err.Error())
|
err = errorf("unparsable member event content: %s", err.Error())
|
||||||
return
|
return
|
||||||
|
@ -132,7 +148,7 @@ func StateNeededForAuth(events []Event) (result StateNeeded) {
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
// Extract the 'content' object from the event if it is m.room.member as we need to know 'membership'
|
// Extract the 'content' object from the event if it is m.room.member as we need to know 'membership'
|
||||||
var content *memberContent
|
var content *memberContent
|
||||||
if event.Type() == "m.room.member" {
|
if event.Type() == MRoomMember {
|
||||||
c, err := newMemberContentFromEvent(event)
|
c, err := newMemberContentFromEvent(event)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
content = &c
|
content = &c
|
||||||
|
@ -151,17 +167,17 @@ func StateNeededForAuth(events []Event) (result StateNeeded) {
|
||||||
|
|
||||||
func accumulateStateNeeded(result *StateNeeded, eventType, sender string, stateKey *string, content *memberContent) (err error) {
|
func accumulateStateNeeded(result *StateNeeded, eventType, sender string, stateKey *string, content *memberContent) (err error) {
|
||||||
switch eventType {
|
switch eventType {
|
||||||
case "m.room.create":
|
case MRoomCreate:
|
||||||
// The create event doesn't require any state to authenticate.
|
// The create event doesn't require any state to authenticate.
|
||||||
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L123
|
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L123
|
||||||
case "m.room.aliases":
|
case MRoomAliases:
|
||||||
// Alias events need:
|
// Alias events need:
|
||||||
// * The create event.
|
// * The create event.
|
||||||
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L128
|
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L128
|
||||||
// Alias events need no further authentication.
|
// Alias events need no further authentication.
|
||||||
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L160
|
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L160
|
||||||
result.Create = true
|
result.Create = true
|
||||||
case "m.room.member":
|
case MRoomMember:
|
||||||
// Member events need:
|
// Member events need:
|
||||||
// * The previous membership of the target.
|
// * The previous membership of the target.
|
||||||
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L355
|
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L355
|
||||||
|
@ -255,27 +271,27 @@ func (a *AuthEvents) AddEvent(event *Event) error {
|
||||||
|
|
||||||
// Create implements AuthEventProvider
|
// Create implements AuthEventProvider
|
||||||
func (a *AuthEvents) Create() (*Event, error) {
|
func (a *AuthEvents) Create() (*Event, error) {
|
||||||
return a.events[StateKeyTuple{"m.room.create", ""}], nil
|
return a.events[StateKeyTuple{MRoomCreate, ""}], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// JoinRules implements AuthEventProvider
|
// JoinRules implements AuthEventProvider
|
||||||
func (a *AuthEvents) JoinRules() (*Event, error) {
|
func (a *AuthEvents) JoinRules() (*Event, error) {
|
||||||
return a.events[StateKeyTuple{"m.room.join_rules", ""}], nil
|
return a.events[StateKeyTuple{MRoomJoinRules, ""}], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PowerLevels implements AuthEventProvider
|
// PowerLevels implements AuthEventProvider
|
||||||
func (a *AuthEvents) PowerLevels() (*Event, error) {
|
func (a *AuthEvents) PowerLevels() (*Event, error) {
|
||||||
return a.events[StateKeyTuple{"m.room.power_levels", ""}], nil
|
return a.events[StateKeyTuple{MRoomPowerLevels, ""}], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Member implements AuthEventProvider
|
// Member implements AuthEventProvider
|
||||||
func (a *AuthEvents) Member(stateKey string) (*Event, error) {
|
func (a *AuthEvents) Member(stateKey string) (*Event, error) {
|
||||||
return a.events[StateKeyTuple{"m.room.member", stateKey}], nil
|
return a.events[StateKeyTuple{MRoomMember, stateKey}], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ThirdPartyInvite implements AuthEventProvider
|
// ThirdPartyInvite implements AuthEventProvider
|
||||||
func (a *AuthEvents) ThirdPartyInvite(stateKey string) (*Event, error) {
|
func (a *AuthEvents) ThirdPartyInvite(stateKey string) (*Event, error) {
|
||||||
return a.events[StateKeyTuple{"m.room.third_party_invite", stateKey}], nil
|
return a.events[StateKeyTuple{MRoomThirdPartyInvite, stateKey}], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAuthEvents returns an AuthEventProvider backed by the given events. New events can be added by
|
// NewAuthEvents returns an AuthEventProvider backed by the given events. New events can be added by
|
||||||
|
@ -306,15 +322,15 @@ func errorf(message string, args ...interface{}) error {
|
||||||
// If there was an error loading the auth events then it returns that error.
|
// If there was an error loading the auth events then it returns that error.
|
||||||
func Allowed(event Event, authEvents AuthEventProvider) error {
|
func Allowed(event Event, authEvents AuthEventProvider) error {
|
||||||
switch event.Type() {
|
switch event.Type() {
|
||||||
case "m.room.create":
|
case MRoomCreate:
|
||||||
return createEventAllowed(event)
|
return createEventAllowed(event)
|
||||||
case "m.room.aliases":
|
case MRoomAliases:
|
||||||
return aliasEventAllowed(event, authEvents)
|
return aliasEventAllowed(event, authEvents)
|
||||||
case "m.room.member":
|
case MRoomMember:
|
||||||
return memberEventAllowed(event, authEvents)
|
return memberEventAllowed(event, authEvents)
|
||||||
case "m.room.power_levels":
|
case MRoomPowerLevels:
|
||||||
return powerLevelsEventAllowed(event, authEvents)
|
return powerLevelsEventAllowed(event, authEvents)
|
||||||
case "m.room.redaction":
|
case MRoomRedaction:
|
||||||
return redactEventAllowed(event, authEvents)
|
return redactEventAllowed(event, authEvents)
|
||||||
default:
|
default:
|
||||||
return defaultEventAllowed(event, authEvents)
|
return defaultEventAllowed(event, authEvents)
|
||||||
|
|
|
@ -191,7 +191,7 @@ func (c *powerLevelContent) userLevel(userID string) int64 {
|
||||||
|
|
||||||
// eventLevel returns the power level needed to send an event in the room.
|
// eventLevel returns the power level needed to send an event in the room.
|
||||||
func (c *powerLevelContent) eventLevel(eventType string, isState bool) int64 {
|
func (c *powerLevelContent) eventLevel(eventType string, isState bool) int64 {
|
||||||
if eventType == "m.room.third_party_invite" {
|
if eventType == MRoomThirdPartyInvite {
|
||||||
// Special case third_party_invite events to have the same level as
|
// Special case third_party_invite events to have the same level as
|
||||||
// m.room.member invite events.
|
// m.room.member invite events.
|
||||||
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L182
|
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L182
|
||||||
|
|
|
@ -201,9 +201,9 @@ func VerifyEventSignatures(events []Event, keyRing KeyRing) error {
|
||||||
}
|
}
|
||||||
toVerify = append(toVerify, v)
|
toVerify = append(toVerify, v)
|
||||||
|
|
||||||
// "m.room.member" invite events are signed by both the server sending
|
// MRoomMember invite events are signed by both the server sending
|
||||||
// the invite and the server the invite is for.
|
// the invite and the server the invite is for.
|
||||||
if event.Type() == "m.room.member" && event.StateKey() != nil {
|
if event.Type() == MRoomMember && event.StateKey() != nil {
|
||||||
targetDomain, err := domainFromID(*event.StateKey())
|
targetDomain, err := domainFromID(*event.StateKey())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -34,6 +34,78 @@ type RespState struct {
|
||||||
AuthEvents []Event `json:"auth_chain"`
|
AuthEvents []Event `json:"auth_chain"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Events combines the auth events and the state events and returns
|
||||||
|
// them in an order where every event comes after its auth events.
|
||||||
|
// Each event will only appear once in the output list.
|
||||||
|
// Returns an error if there are missing auth events or if there is
|
||||||
|
// a cycle in the auth events.
|
||||||
|
func (r RespState) Events() ([]Event, error) {
|
||||||
|
eventsByID := map[string]*Event{}
|
||||||
|
// Collect a map of event reference to event
|
||||||
|
for i := range r.StateEvents {
|
||||||
|
eventsByID[r.StateEvents[i].EventID()] = &r.StateEvents[i]
|
||||||
|
}
|
||||||
|
for i := range r.AuthEvents {
|
||||||
|
eventsByID[r.AuthEvents[i].EventID()] = &r.AuthEvents[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
queued := map[*Event]bool{}
|
||||||
|
outputted := map[*Event]bool{}
|
||||||
|
var result []Event
|
||||||
|
for _, event := range eventsByID {
|
||||||
|
if outputted[event] {
|
||||||
|
// If we've already written the event then we can skip it.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// The code below does a depth first scan through the auth events
|
||||||
|
// looking for events that can be appended to the output.
|
||||||
|
|
||||||
|
// We use an explicit stack rather than using recursion so
|
||||||
|
// that we can check we aren't creating cycles.
|
||||||
|
stack := []*Event{event}
|
||||||
|
|
||||||
|
LoopProcessTopOfStack:
|
||||||
|
for len(stack) > 0 {
|
||||||
|
top := stack[len(stack)-1]
|
||||||
|
// Check if we can output the top of the stack.
|
||||||
|
// We can output it if we have outputted all of its auth_events.
|
||||||
|
for _, ref := range top.AuthEvents() {
|
||||||
|
authEvent := eventsByID[ref.EventID]
|
||||||
|
if authEvent == nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"gomatrixserverlib: missing auth event with ID %q for event %q",
|
||||||
|
ref.EventID, top.EventID(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if outputted[authEvent] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if queued[authEvent] {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"gomatrixserverlib: auth event cycle for ID %q",
|
||||||
|
ref.EventID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// If we haven't visited the auth event yet then we need to
|
||||||
|
// process it before processing the event currently on top of
|
||||||
|
// the stack.
|
||||||
|
stack = append(stack, authEvent)
|
||||||
|
queued[authEvent] = true
|
||||||
|
continue LoopProcessTopOfStack
|
||||||
|
}
|
||||||
|
// If we've processed all the auth events for the event on top of
|
||||||
|
// the stack then we can append it to the result and try processing
|
||||||
|
// the item below it in the stack.
|
||||||
|
result = append(result, *top)
|
||||||
|
outputted[top] = true
|
||||||
|
stack = stack[:len(stack)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Check that a response to /state is valid.
|
// Check that a response to /state is valid.
|
||||||
func (r RespState) Check(keyRing KeyRing) error {
|
func (r RespState) Check(keyRing KeyRing) error {
|
||||||
var allEvents []Event
|
var allEvents []Event
|
||||||
|
|
|
@ -140,17 +140,17 @@ func redactEvent(eventJSON []byte) ([]byte, error) {
|
||||||
// Copy the content fields that we should keep for the event type.
|
// Copy the content fields that we should keep for the event type.
|
||||||
// By default we copy nothing leaving the content object empty.
|
// By default we copy nothing leaving the content object empty.
|
||||||
switch event.Type {
|
switch event.Type {
|
||||||
case "m.room.create":
|
case MRoomCreate:
|
||||||
newContent.createContent = event.Content.createContent
|
newContent.createContent = event.Content.createContent
|
||||||
case "m.room.member":
|
case MRoomMember:
|
||||||
newContent.memberContent = event.Content.memberContent
|
newContent.memberContent = event.Content.memberContent
|
||||||
case "m.room.join_rules":
|
case MRoomJoinRules:
|
||||||
newContent.joinRulesContent = event.Content.joinRulesContent
|
newContent.joinRulesContent = event.Content.joinRulesContent
|
||||||
case "m.room.power_levels":
|
case MRoomPowerLevels:
|
||||||
newContent.powerLevelContent = event.Content.powerLevelContent
|
newContent.powerLevelContent = event.Content.powerLevelContent
|
||||||
case "m.room.history_visibility":
|
case MRoomHistoryVisibility:
|
||||||
newContent.historyVisibilityContent = event.Content.historyVisibilityContent
|
newContent.historyVisibilityContent = event.Content.historyVisibilityContent
|
||||||
case "m.room.aliases":
|
case MRoomAliases:
|
||||||
newContent.aliasesContent = event.Content.aliasesContent
|
newContent.aliasesContent = event.Content.aliasesContent
|
||||||
}
|
}
|
||||||
// Replace the content with our new filtered content.
|
// Replace the content with our new filtered content.
|
||||||
|
|
|
@ -114,24 +114,24 @@ func (r *stateResolver) addConflicted(events []Event) {
|
||||||
// By default we add the event to a block in the others list.
|
// By default we add the event to a block in the others list.
|
||||||
blockList := &r.others
|
blockList := &r.others
|
||||||
switch key.eventType {
|
switch key.eventType {
|
||||||
case "m.room.create":
|
case MRoomCreate:
|
||||||
if key.stateKey == "" {
|
if key.stateKey == "" {
|
||||||
r.creates = append(r.creates, event)
|
r.creates = append(r.creates, event)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case "m.room.power_levels":
|
case MRoomPowerLevels:
|
||||||
if key.stateKey == "" {
|
if key.stateKey == "" {
|
||||||
r.powerLevels = append(r.powerLevels, event)
|
r.powerLevels = append(r.powerLevels, event)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case "m.room.join_rules":
|
case MRoomJoinRules:
|
||||||
if key.stateKey == "" {
|
if key.stateKey == "" {
|
||||||
r.joinRules = append(r.joinRules, event)
|
r.joinRules = append(r.joinRules, event)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case "m.room.member":
|
case MRoomMember:
|
||||||
blockList = &r.members
|
blockList = &r.members
|
||||||
case "m.room.third_party_invite":
|
case MRoomThirdPartyInvite:
|
||||||
blockList = &r.thirdPartyInvites
|
blockList = &r.thirdPartyInvites
|
||||||
}
|
}
|
||||||
// We need to find an entry for the state key in a block list.
|
// We need to find an entry for the state key in a block list.
|
||||||
|
@ -153,21 +153,21 @@ func (r *stateResolver) addConflicted(events []Event) {
|
||||||
// Add an event to the resolved auth events.
|
// Add an event to the resolved auth events.
|
||||||
func (r *stateResolver) addAuthEvent(event *Event) {
|
func (r *stateResolver) addAuthEvent(event *Event) {
|
||||||
switch event.Type() {
|
switch event.Type() {
|
||||||
case "m.room.create":
|
case MRoomCreate:
|
||||||
if event.StateKeyEquals("") {
|
if event.StateKeyEquals("") {
|
||||||
r.resolvedCreate = event
|
r.resolvedCreate = event
|
||||||
}
|
}
|
||||||
case "m.room.power_levels":
|
case MRoomPowerLevels:
|
||||||
if event.StateKeyEquals("") {
|
if event.StateKeyEquals("") {
|
||||||
r.resolvedPowerLevels = event
|
r.resolvedPowerLevels = event
|
||||||
}
|
}
|
||||||
case "m.room.join_rules":
|
case MRoomJoinRules:
|
||||||
if event.StateKeyEquals("") {
|
if event.StateKeyEquals("") {
|
||||||
r.resolvedJoinRules = event
|
r.resolvedJoinRules = event
|
||||||
}
|
}
|
||||||
case "m.room.member":
|
case MRoomMember:
|
||||||
r.resolvedMembers[*event.StateKey()] = event
|
r.resolvedMembers[*event.StateKey()] = event
|
||||||
case "m.room.third_party_invite":
|
case MRoomThirdPartyInvite:
|
||||||
r.resolvedThirdPartyInvites[*event.StateKey()] = event
|
r.resolvedThirdPartyInvites[*event.StateKey()] = event
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("Unexpected auth event with type %q", event.Type()))
|
panic(fmt.Errorf("Unexpected auth event with type %q", event.Type()))
|
||||||
|
@ -177,21 +177,21 @@ func (r *stateResolver) addAuthEvent(event *Event) {
|
||||||
// Remove the auth event with the given type and state key.
|
// Remove the auth event with the given type and state key.
|
||||||
func (r *stateResolver) removeAuthEvent(eventType, stateKey string) {
|
func (r *stateResolver) removeAuthEvent(eventType, stateKey string) {
|
||||||
switch eventType {
|
switch eventType {
|
||||||
case "m.room.create":
|
case MRoomCreate:
|
||||||
if stateKey == "" {
|
if stateKey == "" {
|
||||||
r.resolvedCreate = nil
|
r.resolvedCreate = nil
|
||||||
}
|
}
|
||||||
case "m.room.power_levels":
|
case MRoomPowerLevels:
|
||||||
if stateKey == "" {
|
if stateKey == "" {
|
||||||
r.resolvedPowerLevels = nil
|
r.resolvedPowerLevels = nil
|
||||||
}
|
}
|
||||||
case "m.room.join_rules":
|
case MRoomJoinRules:
|
||||||
if stateKey == "" {
|
if stateKey == "" {
|
||||||
r.resolvedJoinRules = nil
|
r.resolvedJoinRules = nil
|
||||||
}
|
}
|
||||||
case "m.room.member":
|
case MRoomMember:
|
||||||
r.resolvedMembers[stateKey] = nil
|
r.resolvedMembers[stateKey] = nil
|
||||||
case "m.room.third_party_invite":
|
case MRoomThirdPartyInvite:
|
||||||
r.resolvedThirdPartyInvites[stateKey] = nil
|
r.resolvedThirdPartyInvites[stateKey] = nil
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("Unexpected auth event with type %q", eventType))
|
panic(fmt.Errorf("Unexpected auth event with type %q", eventType))
|
||||||
|
|
Loading…
Reference in a new issue