diff --git a/src/github.com/matrix-org/dendrite/clientapi/producers/roomserver.go b/src/github.com/matrix-org/dendrite/clientapi/producers/roomserver.go index b53d74e4f..b1c390127 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/producers/roomserver.go +++ b/src/github.com/matrix-org/dendrite/clientapi/producers/roomserver.go @@ -45,22 +45,61 @@ func NewRoomserverProducer(kafkaURIs []string, topic string) (*RoomserverProduce func (c *RoomserverProducer) SendEvents(events []gomatrixserverlib.Event) error { eventIDs := make([]string, len(events)) ires := make([]api.InputRoomEvent, len(events)) - for i := range events { - var authEventIDs []string - for _, ref := range events[i].AuthEvents() { - authEventIDs = append(authEventIDs, ref.EventID) - } - ire := api.InputRoomEvent{ + for i, event := range events { + ires[i] = api.InputRoomEvent{ Kind: api.KindNew, - Event: events[i].JSON(), - AuthEventIDs: authEventIDs, + Event: event.JSON(), + AuthEventIDs: authEventIDs(event), } - ires[i] = ire - eventIDs[i] = events[i].EventID() + eventIDs[i] = event.EventID() } 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 // arrays must match, and each element must correspond to the same event. func (c *RoomserverProducer) SendInputRoomEvents(ires []api.InputRoomEvent, eventIDs []string) error { diff --git a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go index a7d47c495..d104f6457 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go @@ -28,6 +28,7 @@ import ( "github.com/matrix-org/dendrite/clientapi/writers" "github.com/matrix-org/dendrite/common" "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" "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 // to clients which need to make outbound HTTP requests. -func Setup(servMux *http.ServeMux, httpClient *http.Client, cfg config.ClientAPI, producer *producers.RoomserverProducer, - queryAPI api.RoomserverQueryAPI, accountDB *accounts.Database, deviceDB *devices.Database) { +func Setup( + 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() r0mux := apiMux.PathPrefix(pathPrefixR0).Subrouter() 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) }), ) + 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}", common.MakeAuthAPI("send_message", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse { vars := mux.Vars(req) diff --git a/src/github.com/matrix-org/dendrite/clientapi/writers/joinroom.go b/src/github.com/matrix-org/dendrite/clientapi/writers/joinroom.go new file mode 100644 index 000000000..2b679b56e --- /dev/null +++ b/src/github.com/matrix-org/dendrite/clientapi/writers/joinroom.go @@ -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 +} diff --git a/src/github.com/matrix-org/dendrite/cmd/client-api-proxy/main.go b/src/github.com/matrix-org/dendrite/cmd/client-api-proxy/main.go index 505026756..fb8751cad 100644 --- a/src/github.com/matrix-org/dendrite/cmd/client-api-proxy/main.go +++ b/src/github.com/matrix-org/dendrite/cmd/client-api-proxy/main.go @@ -76,7 +76,11 @@ func makeProxy(targetURL string) (*httputil.ReverseProxy, error) { "url": targetURL, "method": req.Method, }).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 { // We already checked that we can parse the URL // So this shouldn't ever get hit. diff --git a/src/github.com/matrix-org/dendrite/cmd/dendrite-client-api-server/main.go b/src/github.com/matrix-org/dendrite/cmd/dendrite-client-api-server/main.go index e0a6616f2..fd1df9a30 100644 --- a/src/github.com/matrix-org/dendrite/cmd/dendrite-client-api-server/main.go +++ b/src/github.com/matrix-org/dendrite/cmd/dendrite-client-api-server/main.go @@ -26,6 +26,7 @@ import ( "github.com/matrix-org/dendrite/clientapi/routing" "github.com/matrix-org/dendrite/common" "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/gomatrixserverlib" log "github.com/Sirupsen/logrus" @@ -81,6 +82,8 @@ func main() { 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) accountDB, err := accounts.NewDatabase(accountDataSource, serverName) if err != nil { @@ -91,6 +94,32 @@ func main() { 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)) } + +// 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 +} diff --git a/src/github.com/matrix-org/dendrite/cmd/dendrite-federation-api-server/main.go b/src/github.com/matrix-org/dendrite/cmd/dendrite-federation-api-server/main.go index 220a3d9bc..15bf5bc47 100644 --- a/src/github.com/matrix-org/dendrite/cmd/dendrite-federation-api-server/main.go +++ b/src/github.com/matrix-org/dendrite/cmd/dendrite-federation-api-server/main.go @@ -15,6 +15,7 @@ package main import ( + "encoding/base64" "net/http" "os" "time" @@ -32,6 +33,14 @@ var ( logDir = os.Getenv("LOG_DIR") serverName = gomatrixserverlib.ServerName(os.Getenv("SERVER_NAME")) 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() { @@ -44,6 +53,10 @@ func main() { serverName = "localhost" } + if tlsFingerprint == "" { + log.Panic("No TLS_FINGERPRINT environment variable found.") + } + cfg := config.FederationAPI{ ServerName: serverName, // TODO: make the validity period configurable. @@ -56,6 +69,12 @@ func main() { 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) log.Fatal(http.ListenAndServe(bindAddr, nil)) } diff --git a/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go b/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go index 41486de29..b5af66dc6 100644 --- a/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go @@ -24,7 +24,7 @@ import ( ) const ( - pathPrefixV2Keys = "/_matrix/keys/v2" + pathPrefixV2Keys = "/_matrix/key/v2" ) // Setup registers HTTP handlers with the given ServeMux. @@ -32,11 +32,16 @@ func Setup(servMux *http.ServeMux, cfg config.FederationAPI) { apiMux := mux.NewRouter() v2keysmux := apiMux.PathPrefix(pathPrefixV2Keys).Subrouter() - v2keysmux.Handle("/server/", - makeAPI("localkeys", func(req *http.Request) util.JSONResponse { - return readers.LocalKeys(req, cfg) - }), - ) + localKeys := makeAPI("localkeys", func(req *http.Request) util.JSONResponse { + 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("/api/", http.StripPrefix("/api", apiMux)) diff --git a/vendor/manifest b/vendor/manifest index 8df0f6a97..e76c66ccf 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -98,7 +98,7 @@ { "importpath": "github.com/matrix-org/gomatrixserverlib", "repository": "https://github.com/matrix-org/gomatrixserverlib", - "revision": "9cefcd6c3a00bff51e719a33e19a16edf52cdd6f", + "revision": "c396ef3cc1e546729f7052f1f48e345cc59269f4", "branch": "master" }, { diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/event.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/event.go index e26484d63..11a165131 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/event.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/event.go @@ -386,7 +386,7 @@ func (e Event) CheckFields() error { // are allowed to have a different sender because they have the same // 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 - if e.fields.Type != "m.room.member" { + if e.fields.Type != MRoomMember { return fmt.Errorf( "gomatrixserverlib: sender domain doesn't match origin: %q != %q", eventDomain, e.fields.Origin, diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/eventauth.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/eventauth.go index e3d790990..15c47bfc3 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/eventauth.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/eventauth.go @@ -28,6 +28,22 @@ const ( leave = "leave" invite = "invite" 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. @@ -47,19 +63,19 @@ type StateNeeded struct { // Tuples returns the needed state key tuples for performing auth on an event. func (s StateNeeded) Tuples() (res []StateKeyTuple) { if s.Create { - res = append(res, StateKeyTuple{"m.room.create", ""}) + res = append(res, StateKeyTuple{MRoomCreate, ""}) } if s.JoinRules { - res = append(res, StateKeyTuple{"m.room.join_rules", ""}) + res = append(res, StateKeyTuple{MRoomJoinRules, ""}) } if s.PowerLevels { - res = append(res, StateKeyTuple{"m.room.power_levels", ""}) + res = append(res, StateKeyTuple{MRoomPowerLevels, ""}) } for _, userID := range s.Member { - res = append(res, StateKeyTuple{"m.room.member", userID}) + res = append(res, StateKeyTuple{MRoomMember, userID}) } for _, token := range s.ThirdPartyInvite { - res = append(res, StateKeyTuple{"m.room.third_party_invite", token}) + res = append(res, StateKeyTuple{MRoomThirdPartyInvite, token}) } return } @@ -114,7 +130,7 @@ func (s StateNeeded) AuthEventReferences(provider AuthEventProvider) (refs []Eve 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' var content *memberContent - if builder.Type == "m.room.member" { + if builder.Type == MRoomMember { if err = json.Unmarshal(builder.Content, &content); err != nil { err = errorf("unparsable member event content: %s", err.Error()) return @@ -132,7 +148,7 @@ func StateNeededForAuth(events []Event) (result StateNeeded) { for _, event := range events { // Extract the 'content' object from the event if it is m.room.member as we need to know 'membership' var content *memberContent - if event.Type() == "m.room.member" { + if event.Type() == MRoomMember { c, err := newMemberContentFromEvent(event) if err == nil { 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) { switch eventType { - case "m.room.create": + case MRoomCreate: // 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 - case "m.room.aliases": + case MRoomAliases: // Alias events need: // * The create event. // https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L128 // Alias events need no further authentication. // https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L160 result.Create = true - case "m.room.member": + case MRoomMember: // Member events need: // * The previous membership of the target. // 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 func (a *AuthEvents) Create() (*Event, error) { - return a.events[StateKeyTuple{"m.room.create", ""}], nil + return a.events[StateKeyTuple{MRoomCreate, ""}], nil } // JoinRules implements AuthEventProvider func (a *AuthEvents) JoinRules() (*Event, error) { - return a.events[StateKeyTuple{"m.room.join_rules", ""}], nil + return a.events[StateKeyTuple{MRoomJoinRules, ""}], nil } // PowerLevels implements AuthEventProvider func (a *AuthEvents) PowerLevels() (*Event, error) { - return a.events[StateKeyTuple{"m.room.power_levels", ""}], nil + return a.events[StateKeyTuple{MRoomPowerLevels, ""}], nil } // Member implements AuthEventProvider 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 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 @@ -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. func Allowed(event Event, authEvents AuthEventProvider) error { switch event.Type() { - case "m.room.create": + case MRoomCreate: return createEventAllowed(event) - case "m.room.aliases": + case MRoomAliases: return aliasEventAllowed(event, authEvents) - case "m.room.member": + case MRoomMember: return memberEventAllowed(event, authEvents) - case "m.room.power_levels": + case MRoomPowerLevels: return powerLevelsEventAllowed(event, authEvents) - case "m.room.redaction": + case MRoomRedaction: return redactEventAllowed(event, authEvents) default: return defaultEventAllowed(event, authEvents) diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/eventcontent.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/eventcontent.go index 97f9316b2..27babcb4a 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/eventcontent.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/eventcontent.go @@ -191,7 +191,7 @@ func (c *powerLevelContent) userLevel(userID string) int64 { // eventLevel returns the power level needed to send an event in the room. 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 // m.room.member invite events. // https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L182 diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/eventcrypto.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/eventcrypto.go index beeddd3e2..ed52562f0 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/eventcrypto.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/eventcrypto.go @@ -201,9 +201,9 @@ func VerifyEventSignatures(events []Event, keyRing KeyRing) error { } 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. - if event.Type() == "m.room.member" && event.StateKey() != nil { + if event.Type() == MRoomMember && event.StateKey() != nil { targetDomain, err := domainFromID(*event.StateKey()) if err != nil { return err diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/federationtypes.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/federationtypes.go index cfddab910..1f9c81eb1 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/federationtypes.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/federationtypes.go @@ -34,6 +34,78 @@ type RespState struct { 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. func (r RespState) Check(keyRing KeyRing) error { var allEvents []Event diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/redactevent.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/redactevent.go index f54677356..06a7cd284 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/redactevent.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/redactevent.go @@ -140,17 +140,17 @@ func redactEvent(eventJSON []byte) ([]byte, error) { // Copy the content fields that we should keep for the event type. // By default we copy nothing leaving the content object empty. switch event.Type { - case "m.room.create": + case MRoomCreate: newContent.createContent = event.Content.createContent - case "m.room.member": + case MRoomMember: newContent.memberContent = event.Content.memberContent - case "m.room.join_rules": + case MRoomJoinRules: newContent.joinRulesContent = event.Content.joinRulesContent - case "m.room.power_levels": + case MRoomPowerLevels: newContent.powerLevelContent = event.Content.powerLevelContent - case "m.room.history_visibility": + case MRoomHistoryVisibility: newContent.historyVisibilityContent = event.Content.historyVisibilityContent - case "m.room.aliases": + case MRoomAliases: newContent.aliasesContent = event.Content.aliasesContent } // Replace the content with our new filtered content. diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/stateresolution.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/stateresolution.go index 81cdafd7c..a43dd8ef8 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/stateresolution.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/stateresolution.go @@ -114,24 +114,24 @@ func (r *stateResolver) addConflicted(events []Event) { // By default we add the event to a block in the others list. blockList := &r.others switch key.eventType { - case "m.room.create": + case MRoomCreate: if key.stateKey == "" { r.creates = append(r.creates, event) continue } - case "m.room.power_levels": + case MRoomPowerLevels: if key.stateKey == "" { r.powerLevels = append(r.powerLevels, event) continue } - case "m.room.join_rules": + case MRoomJoinRules: if key.stateKey == "" { r.joinRules = append(r.joinRules, event) continue } - case "m.room.member": + case MRoomMember: blockList = &r.members - case "m.room.third_party_invite": + case MRoomThirdPartyInvite: blockList = &r.thirdPartyInvites } // 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. func (r *stateResolver) addAuthEvent(event *Event) { switch event.Type() { - case "m.room.create": + case MRoomCreate: if event.StateKeyEquals("") { r.resolvedCreate = event } - case "m.room.power_levels": + case MRoomPowerLevels: if event.StateKeyEquals("") { r.resolvedPowerLevels = event } - case "m.room.join_rules": + case MRoomJoinRules: if event.StateKeyEquals("") { r.resolvedJoinRules = event } - case "m.room.member": + case MRoomMember: r.resolvedMembers[*event.StateKey()] = event - case "m.room.third_party_invite": + case MRoomThirdPartyInvite: r.resolvedThirdPartyInvites[*event.StateKey()] = event default: 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. func (r *stateResolver) removeAuthEvent(eventType, stateKey string) { switch eventType { - case "m.room.create": + case MRoomCreate: if stateKey == "" { r.resolvedCreate = nil } - case "m.room.power_levels": + case MRoomPowerLevels: if stateKey == "" { r.resolvedPowerLevels = nil } - case "m.room.join_rules": + case MRoomJoinRules: if stateKey == "" { r.resolvedJoinRules = nil } - case "m.room.member": + case MRoomMember: r.resolvedMembers[stateKey] = nil - case "m.room.third_party_invite": + case MRoomThirdPartyInvite: r.resolvedThirdPartyInvites[stateKey] = nil default: panic(fmt.Errorf("Unexpected auth event with type %q", eventType))