diff --git a/clientapi/routing/capabilities.go b/clientapi/routing/capabilities.go index 0c583055..1792c630 100644 --- a/clientapi/routing/capabilities.go +++ b/clientapi/routing/capabilities.go @@ -29,7 +29,7 @@ func GetCapabilities( req *http.Request, queryAPI roomserverAPI.RoomserverQueryAPI, ) util.JSONResponse { roomVersionsQueryReq := roomserverAPI.QueryRoomVersionCapabilitiesRequest{} - var roomVersionsQueryRes roomserverAPI.QueryRoomVersionCapabilitiesResponse + roomVersionsQueryRes := roomserverAPI.QueryRoomVersionCapabilitiesResponse{} if err := queryAPI.QueryRoomVersionCapabilities( req.Context(), &roomVersionsQueryReq, diff --git a/clientapi/routing/joinroom.go b/clientapi/routing/joinroom.go index 3ca7d052..0f1a9ba4 100644 --- a/clientapi/routing/joinroom.go +++ b/clientapi/routing/joinroom.go @@ -242,6 +242,9 @@ func (r joinRoomReq) joinRoomUsingServers( queryRes := roomserverAPI.QueryLatestEventsAndStateResponse{} event, err := common.BuildEvent(r.req.Context(), &eb, r.cfg, r.evTime, r.queryAPI, &queryRes) if err == nil { + // If we have successfully built an event at this point then we can + // assert that the room is a local room, as BuildEvent was able to + // add prev_events etc successfully. if _, err = r.producer.SendEvents( r.req.Context(), []gomatrixserverlib.HeaderedEvent{ @@ -260,6 +263,10 @@ func (r joinRoomReq) joinRoomUsingServers( }{roomID}, } } + + // Otherwise, if we've reached here, then we haven't been able to populate + // prev_events etc for the room, therefore the room is probably federated. + // TODO: This needs to be re-thought, as in the case of an invite, the room // will exist in the database in roomserver_rooms but won't have any state // events, therefore this below check fails. @@ -323,14 +330,14 @@ func (r joinRoomReq) joinRoomUsingServer(roomID string, server gomatrixserverlib respMakeJoin, err := r.federation.MakeJoin(r.req.Context(), server, roomID, r.userID, supportedVersions) if err != nil { // TODO: Check if the user was not allowed to join the room. - return nil, err + return nil, fmt.Errorf("r.federation.MakeJoin: %w", 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" err = r.writeToBuilder(&respMakeJoin.JoinEvent, roomID) if err != nil { - return nil, err + return nil, fmt.Errorf("r.writeToBuilder: %w", err) } if respMakeJoin.RoomVersion == "" { @@ -350,18 +357,16 @@ func (r joinRoomReq) joinRoomUsingServer(roomID string, server gomatrixserverlib r.cfg.Matrix.PrivateKey, respMakeJoin.RoomVersion, ) if err != nil { - util.GetLogger(r.req.Context()).WithError(err).Error("respMakeJoin.JoinEvent.Build failed") - res := jsonerror.InternalServerError() - return &res, nil + return nil, fmt.Errorf("respMakeJoin.JoinEvent.Build: %w", err) } respSendJoin, err := r.federation.SendJoin(r.req.Context(), server, event, respMakeJoin.RoomVersion) if err != nil { - return nil, err + return nil, fmt.Errorf("r.federation.SendJoin: %w", err) } if err = respSendJoin.Check(r.req.Context(), r.keyRing, event); err != nil { - return nil, err + return nil, fmt.Errorf("respSendJoin: %w", err) } if err = r.producer.SendEventWithState( @@ -369,9 +374,7 @@ func (r joinRoomReq) joinRoomUsingServer(roomID string, server gomatrixserverlib gomatrixserverlib.RespState(respSendJoin.RespState), event.Headered(respMakeJoin.RoomVersion), ); err != nil { - util.GetLogger(r.req.Context()).WithError(err).Error("gomatrixserverlib.RespState failed") - res := jsonerror.InternalServerError() - return &res, nil + return nil, fmt.Errorf("r.producer.SendEventWithState: %w", err) } return &util.JSONResponse{ diff --git a/common/events.go b/common/events.go index b79998a7..adbdf338 100644 --- a/common/events.go +++ b/common/events.go @@ -17,6 +17,7 @@ package common import ( "context" "errors" + "fmt" "time" "github.com/matrix-org/dendrite/common/config" @@ -46,6 +47,7 @@ func BuildEvent( err := AddPrevEventsToEvent(ctx, builder, queryAPI, queryRes) if err != nil { + // This can pass through a ErrRoomNoExists to the caller return nil, err } @@ -68,7 +70,7 @@ func AddPrevEventsToEvent( ) error { eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(builder) if err != nil { - return err + return fmt.Errorf("gomatrixserverlib.StateNeededForEventBuilder: %w", err) } // Ask the roomserver for information about this room @@ -77,7 +79,7 @@ func AddPrevEventsToEvent( StateToFetch: eventsNeeded.Tuples(), } if err = queryAPI.QueryLatestEventsAndState(ctx, &queryReq, queryRes); err != nil { - return err + return fmt.Errorf("queryAPI.QueryLatestEventsAndState: %w", err) } if !queryRes.RoomExists { @@ -86,7 +88,7 @@ func AddPrevEventsToEvent( eventFormat, err := queryRes.RoomVersion.EventFormat() if err != nil { - return err + return fmt.Errorf("queryRes.RoomVersion.EventFormat: %w", err) } builder.Depth = queryRes.Depth @@ -96,26 +98,26 @@ func AddPrevEventsToEvent( for i := range queryRes.StateEvents { err = authEvents.AddEvent(&queryRes.StateEvents[i].Event) if err != nil { - return err + return fmt.Errorf("authEvents.AddEvent: %w", err) } } refs, err := eventsNeeded.AuthEventReferences(&authEvents) if err != nil { - return err + return fmt.Errorf("eventsNeeded.AuthEventReferences: %w", err) } + truncAuth, truncPrev := truncateAuthAndPrevEvents(refs, queryRes.LatestEvents) switch eventFormat { case gomatrixserverlib.EventFormatV1: - builder.AuthEvents = refs - builder.PrevEvents = queryRes.LatestEvents + builder.AuthEvents = truncAuth + builder.PrevEvents = truncPrev case gomatrixserverlib.EventFormatV2: - v2AuthRefs := []string{} - v2PrevRefs := []string{} - for _, ref := range refs { + v2AuthRefs, v2PrevRefs := []string{}, []string{} + for _, ref := range truncAuth { v2AuthRefs = append(v2AuthRefs, ref.EventID) } - for _, ref := range queryRes.LatestEvents { + for _, ref := range truncPrev { v2PrevRefs = append(v2PrevRefs, ref.EventID) } builder.AuthEvents = v2AuthRefs @@ -124,3 +126,21 @@ func AddPrevEventsToEvent( return nil } + +// truncateAuthAndPrevEvents limits the number of events we add into +// an event as prev_events or auth_events. +// NOTSPEC: The limits here feel a bit arbitrary but they are currently +// here because of https://github.com/matrix-org/matrix-doc/issues/2307 +// and because Synapse will just drop events that don't comply. +func truncateAuthAndPrevEvents(auth, prev []gomatrixserverlib.EventReference) ( + truncAuth, truncPrev []gomatrixserverlib.EventReference, +) { + truncAuth, truncPrev = auth, prev + if len(truncAuth) > 10 { + truncAuth = truncAuth[:10] + } + if len(truncPrev) > 20 { + truncPrev = truncPrev[:20] + } + return +} diff --git a/federationapi/routing/join.go b/federationapi/routing/join.go index a39ff639..0a7b2300 100644 --- a/federationapi/routing/join.go +++ b/federationapi/routing/join.go @@ -15,6 +15,7 @@ package routing import ( + "fmt" "net/http" "time" @@ -34,6 +35,7 @@ func MakeJoin( cfg *config.Dendrite, query api.RoomserverQueryAPI, roomID, userID string, + remoteVersions []gomatrixserverlib.RoomVersion, ) util.JSONResponse { verReq := api.QueryRoomVersionForRoomRequest{RoomID: roomID} verRes := api.QueryRoomVersionForRoomResponse{} @@ -44,6 +46,27 @@ func MakeJoin( } } + // Check that the room that the remote side is trying to join is actually + // one of the room versions that they listed in their supported ?ver= in + // the make_join URL. + // https://matrix.org/docs/spec/server_server/r0.1.3#get-matrix-federation-v1-make-join-roomid-userid + remoteSupportsVersion := false + for _, v := range remoteVersions { + if v == verRes.RoomVersion { + remoteSupportsVersion = true + break + } + } + // If it isn't, stop trying to join the room. + if !remoteSupportsVersion { + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.UnsupportedRoomVersion( + fmt.Sprintf("Joining server does not support room version %s", verRes.RoomVersion), + ), + } + } + _, domain, err := gomatrixserverlib.SplitID('@', userID) if err != nil { return util.JSONResponse{ @@ -140,7 +163,12 @@ func SendJoin( 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"), + JSON: jsonerror.BadJSON( + fmt.Sprintf( + "The room ID in the request path (%q) must match the room ID in the join event JSON (%q)", + roomID, event.RoomID(), + ), + ), } } @@ -148,7 +176,12 @@ func SendJoin( 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"), + JSON: jsonerror.BadJSON( + fmt.Sprintf( + "The event ID in the request path (%q) must match the event ID in the join event JSON (%q)", + eventID, event.EventID(), + ), + ), } } @@ -186,6 +219,7 @@ func SendJoin( PrevEventIDs: event.PrevEventIDs(), AuthEventIDs: event.AuthEventIDs(), RoomID: roomID, + ResolveState: true, }, &stateAndAuthChainResponse) if err != nil { util.GetLogger(httpReq.Context()).WithError(err).Error("query.QueryStateAndAuthChain failed") diff --git a/federationapi/routing/routing.go b/federationapi/routing/routing.go index a2b9dc21..83bac555 100644 --- a/federationapi/routing/routing.go +++ b/federationapi/routing/routing.go @@ -198,7 +198,7 @@ func Setup( }, )).Methods(http.MethodGet) - v1fedmux.Handle("/make_join/{roomID}/{userID}", common.MakeFedAPI( + v1fedmux.Handle("/make_join/{roomID}/{eventID}", common.MakeFedAPI( "federation_make_join", cfg.Matrix.ServerName, keys, func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse { vars, err := common.URLDecodeMapValues(mux.Vars(httpReq)) @@ -206,14 +206,28 @@ func Setup( return util.ErrorResponse(err) } roomID := vars["roomID"] - userID := vars["userID"] + eventID := vars["eventID"] + queryVars := httpReq.URL.Query() + remoteVersions := []gomatrixserverlib.RoomVersion{} + if vers, ok := queryVars["ver"]; ok { + // The remote side supplied a ?=ver so use that to build up the list + // of supported room versions + for _, v := range vers { + remoteVersions = append(remoteVersions, gomatrixserverlib.RoomVersion(v)) + } + } else { + // The remote side didn't supply a ?ver= so just assume that they only + // support room version 1, as per the spec + // https://matrix.org/docs/spec/server_server/r0.1.3#get-matrix-federation-v1-make-join-roomid-userid + remoteVersions = append(remoteVersions, gomatrixserverlib.RoomVersionV1) + } return MakeJoin( - httpReq, request, cfg, query, roomID, userID, + httpReq, request, cfg, query, roomID, eventID, remoteVersions, ) }, )).Methods(http.MethodGet) - v2fedmux.Handle("/send_join/{roomID}/{userID}", common.MakeFedAPI( + v2fedmux.Handle("/send_join/{roomID}/{eventID}", common.MakeFedAPI( "federation_send_join", cfg.Matrix.ServerName, keys, func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse { vars, err := common.URLDecodeMapValues(mux.Vars(httpReq)) @@ -221,14 +235,14 @@ func Setup( return util.ErrorResponse(err) } roomID := vars["roomID"] - userID := vars["userID"] + eventID := vars["eventID"] return SendJoin( - httpReq, request, cfg, query, producer, keys, roomID, userID, + httpReq, request, cfg, query, producer, keys, roomID, eventID, ) }, )).Methods(http.MethodPut) - v1fedmux.Handle("/make_leave/{roomID}/{userID}", common.MakeFedAPI( + v1fedmux.Handle("/make_leave/{roomID}/{eventID}", common.MakeFedAPI( "federation_make_leave", cfg.Matrix.ServerName, keys, func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse { vars, err := common.URLDecodeMapValues(mux.Vars(httpReq)) @@ -236,14 +250,14 @@ func Setup( return util.ErrorResponse(err) } roomID := vars["roomID"] - userID := vars["userID"] + eventID := vars["eventID"] return MakeLeave( - httpReq, request, cfg, query, roomID, userID, + httpReq, request, cfg, query, roomID, eventID, ) }, )).Methods(http.MethodGet) - v2fedmux.Handle("/send_leave/{roomID}/{userID}", common.MakeFedAPI( + v2fedmux.Handle("/send_leave/{roomID}/{eventID}", common.MakeFedAPI( "federation_send_leave", cfg.Matrix.ServerName, keys, func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse { vars, err := common.URLDecodeMapValues(mux.Vars(httpReq)) @@ -251,9 +265,9 @@ func Setup( return util.ErrorResponse(err) } roomID := vars["roomID"] - userID := vars["userID"] + eventID := vars["eventID"] return SendLeave( - httpReq, request, cfg, producer, keys, roomID, userID, + httpReq, request, cfg, producer, keys, roomID, eventID, ) }, )).Methods(http.MethodPut) diff --git a/federationapi/routing/state.go b/federationapi/routing/state.go index 6a47882b..548598dd 100644 --- a/federationapi/routing/state.go +++ b/federationapi/routing/state.go @@ -107,7 +107,6 @@ func getState( return nil, &util.JSONResponse{Code: http.StatusNotFound, JSON: nil} } - prevEventIDs := getIDsFromEventRef(event.PrevEvents()) authEventIDs := getIDsFromEventRef(event.AuthEvents()) var response api.QueryStateAndAuthChainResponse @@ -115,7 +114,7 @@ func getState( ctx, &api.QueryStateAndAuthChainRequest{ RoomID: roomID, - PrevEventIDs: prevEventIDs, + PrevEventIDs: []string{eventID}, AuthEventIDs: authEventIDs, }, &response, diff --git a/go.mod b/go.mod index 16350bb9..5854e552 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/matrix-org/go-http-js-libp2p v0.0.0-20200318135427-31631a9ef51f github.com/matrix-org/go-sqlite3-js v0.0.0-20200325174927-327088cdef10 github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26 - github.com/matrix-org/gomatrixserverlib v0.0.0-20200402141635-4a6e1ade46f8 + github.com/matrix-org/gomatrixserverlib v0.0.0-20200409140603-8b9a51fe9b89 github.com/matrix-org/naffka v0.0.0-20200127221512-0716baaabaf1 github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7 github.com/mattn/go-sqlite3 v2.0.2+incompatible @@ -23,9 +23,9 @@ require ( github.com/tidwall/pretty v1.0.1 // indirect github.com/uber/jaeger-client-go v2.22.1+incompatible github.com/uber/jaeger-lib v2.2.0+incompatible - go.uber.org/atomic v1.6.0 // indirect + go.uber.org/atomic v1.6.0 golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d - golang.org/x/net v0.0.0-20190909003024-a7b16738d86b // indirect + golang.org/x/tools v0.0.0-20200402223321-bcf690261a44 // indirect gopkg.in/Shopify/sarama.v1 v1.20.1 gopkg.in/h2non/bimg.v1 v1.0.18 gopkg.in/yaml.v2 v2.2.5 diff --git a/go.sum b/go.sum index f6b268c4..b1f5ac2f 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26 h1:Hr3zjRsq2bh github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0= github.com/matrix-org/gomatrixserverlib v0.0.0-20200124100636-0c2ec91d1df5 h1:kmRjpmFOenVpOaV/DRlo9p6z/IbOKlUC+hhKsAAh8Qg= github.com/matrix-org/gomatrixserverlib v0.0.0-20200124100636-0c2ec91d1df5/go.mod h1:FsKa2pWE/bpQql9H7U4boOPXFoJX/QcqaZZ6ijLkaZI= -github.com/matrix-org/gomatrixserverlib v0.0.0-20200402141635-4a6e1ade46f8 h1:VZ7xGklSuzU9geMekuxKO4FvUBUaPjP+8IkcwzQtqOI= -github.com/matrix-org/gomatrixserverlib v0.0.0-20200402141635-4a6e1ade46f8/go.mod h1:FsKa2pWE/bpQql9H7U4boOPXFoJX/QcqaZZ6ijLkaZI= +github.com/matrix-org/gomatrixserverlib v0.0.0-20200409140603-8b9a51fe9b89 h1:YAlUJK/Ty2ZrP/DL41CiR0Cp3pteshnyIS420KVs220= +github.com/matrix-org/gomatrixserverlib v0.0.0-20200409140603-8b9a51fe9b89/go.mod h1:FsKa2pWE/bpQql9H7U4boOPXFoJX/QcqaZZ6ijLkaZI= github.com/matrix-org/naffka v0.0.0-20200127221512-0716baaabaf1 h1:osLoFdOy+ChQqVUn2PeTDETFftVkl4w9t/OW18g3lnk= github.com/matrix-org/naffka v0.0.0-20200127221512-0716baaabaf1/go.mod h1:cXoYQIENbdWIQHt1SyCo6Bl3C3raHwJ0wgVrXHSqf+A= github.com/matrix-org/util v0.0.0-20171127121716-2e2df66af2f5 h1:W7l5CP4V7wPyPb4tYE11dbmeAOwtFQBTW0rf4OonOS8= @@ -253,6 +253,7 @@ github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/Aaua github.com/uber/jaeger-lib v2.2.0+incompatible h1:MxZXOiR2JuoANZ3J6DE/U0kSFv/eJ/GfSYVCjK7dyaw= github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.0 h1:vs7fgriifsPbGdK3bNuMWapNn3qnZhCRXc19NRdq010= go.uber.org/atomic v1.3.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -276,6 +277,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -288,8 +291,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190909003024-a7b16738d86b h1:XfVGCX+0T4WOStkaOsJRllbsiImhB2jgVBGc9L0lPGc= -golang.org/x/net v0.0.0-20190909003024-a7b16738d86b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -324,7 +327,11 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c h1:IGkKhmfzcztjm6gYkykvu/NiS8kaqbCWAEWWAyf8J5U= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200402223321-bcf690261a44 h1:bMm0eoDiGkM5VfIyKjxDvoflW5GLp7+VCo+60n8F+TE= +golang.org/x/tools v0.0.0-20200402223321-bcf690261a44/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= diff --git a/roomserver/api/query.go b/roomserver/api/query.go index 9120da4b..5f024d26 100644 --- a/roomserver/api/query.go +++ b/roomserver/api/query.go @@ -203,6 +203,9 @@ type QueryStateAndAuthChainRequest struct { PrevEventIDs []string `json:"prev_event_ids"` // The list of auth events for the event. Used to calculate the auth chain AuthEventIDs []string `json:"auth_event_ids"` + // Should state resolution be ran on the result events? + // TODO: check call sites and remove if we always want to do state res + ResolveState bool `json:"resolve_state"` } // QueryStateAndAuthChainResponse is a response to QueryStateAndAuthChain diff --git a/roomserver/query/query.go b/roomserver/query/query.go index b7cdf150..7e05fe36 100644 --- a/roomserver/query/query.go +++ b/roomserver/query/query.go @@ -132,7 +132,7 @@ func (r *RoomserverQueryAPI) QueryLatestEventsAndState( return err } - // Look up the currrent state for the requested tuples. + // Look up the current state for the requested tuples. stateEntries, err := roomState.LoadStateAtSnapshotForStringTuples( ctx, currentStateSnapshotNID, request.StateToFetch, ) @@ -736,6 +736,14 @@ func (r *RoomserverQueryAPI) QueryStateAndAuthChain( return err } + if request.ResolveState { + if stateEvents, err = state.ResolveConflictsAdhoc( + roomVersion, stateEvents, authEvents, + ); err != nil { + return err + } + } + for _, event := range stateEvents { response.StateEvents = append(response.StateEvents, event.Headered(roomVersion)) } diff --git a/roomserver/state/state.go b/roomserver/state/state.go index 94873dbe..3f68e074 100644 --- a/roomserver/state/state.go +++ b/roomserver/state/state.go @@ -18,7 +18,6 @@ package state import ( "context" - "errors" "fmt" "sort" "time" @@ -681,6 +680,83 @@ func (v StateResolution) calculateStateAfterManyEvents( return } +// ResolveConflictsAdhoc is a helper function to assist the query API in +// performing state resolution when requested. This is a different code +// path to the rest of state.go because this assumes you already have +// gomatrixserverlib.Event objects and not just a bunch of NIDs like +// elsewhere in the state resolution. +// TODO: Some of this can possibly be deduplicated +func ResolveConflictsAdhoc( + version gomatrixserverlib.RoomVersion, + events []gomatrixserverlib.Event, + authEvents []gomatrixserverlib.Event, +) ([]gomatrixserverlib.Event, error) { + type stateKeyTuple struct { + Type string + StateKey string + } + + // Prepare our data structures. + eventMap := make(map[stateKeyTuple][]gomatrixserverlib.Event) + var conflicted, notConflicted, resolved []gomatrixserverlib.Event + + // Run through all of the events that we were given and sort them + // into a map, sorted by (event_type, state_key) tuple. This means + // that we can easily spot events that are "conflicted", e.g. + // there are duplicate values for the same tuple key. + for _, event := range events { + if event.StateKey() == nil { + // Ignore events that are not state events. + continue + } + // Append the events if there is already a conflicted list for + // this tuple key, create it if not. + tuple := stateKeyTuple{event.Type(), *event.StateKey()} + if _, ok := eventMap[tuple]; ok { + eventMap[tuple] = append(eventMap[tuple], event) + } else { + eventMap[tuple] = []gomatrixserverlib.Event{event} + } + } + + // Split out the events in the map into conflicted and unconflicted + // buckets. The conflicted events will be ran through state res, + // whereas unconfliced events will always going to appear in the + // final resolved state. + for _, list := range eventMap { + if len(list) > 1 { + conflicted = append(conflicted, list...) + } else { + notConflicted = append(notConflicted, list...) + } + } + + // Work out which state resolution algorithm we want to run for + // the room version. + stateResAlgo, err := version.StateResAlgorithm() + if err != nil { + return nil, err + } + switch stateResAlgo { + case gomatrixserverlib.StateResV1: + // Currently state res v1 doesn't handle unconflicted events + // for us, like state res v2 does, so we will need to add the + // unconflicted events into the state ourselves. + // TODO: Fix state res v1 so this is handled for the caller. + resolved = gomatrixserverlib.ResolveStateConflicts(conflicted, authEvents) + resolved = append(resolved, notConflicted...) + case gomatrixserverlib.StateResV2: + // TODO: auth difference here? + resolved = gomatrixserverlib.ResolveStateConflictsV2(conflicted, notConflicted, authEvents, authEvents) + default: + return nil, fmt.Errorf("unsupported state resolution algorithm %v", stateResAlgo) + } + + // Return the final resolved state events, including both the + // resolved set of conflicted events, and the unconflicted events. + return resolved, nil +} + func (v StateResolution) resolveConflicts( ctx context.Context, version gomatrixserverlib.RoomVersion, notConflicted, conflicted []types.StateEntry, @@ -695,7 +771,7 @@ func (v StateResolution) resolveConflicts( case gomatrixserverlib.StateResV2: return v.resolveConflictsV2(ctx, notConflicted, conflicted) } - return nil, errors.New("unsupported state resolution algorithm") + return nil, fmt.Errorf("unsupported state resolution algorithm %v", stateResAlgo) } // resolveConflicts resolves a list of conflicted state entries. It takes two lists. diff --git a/roomserver/version/version.go b/roomserver/version/version.go index ed16ecca..e60b5ef7 100644 --- a/roomserver/version/version.go +++ b/roomserver/version/version.go @@ -43,12 +43,12 @@ var roomVersions = map[gomatrixserverlib.RoomVersion]RoomVersionDescription{ Stable: true, }, gomatrixserverlib.RoomVersionV3: RoomVersionDescription{ - Supported: false, - Stable: false, + Supported: true, + Stable: true, }, gomatrixserverlib.RoomVersionV4: RoomVersionDescription{ - Supported: false, - Stable: false, + Supported: true, + Stable: true, }, gomatrixserverlib.RoomVersionV5: RoomVersionDescription{ Supported: false, diff --git a/sytest-blacklist b/sytest-blacklist index 2e6f2057..da0667a1 100644 --- a/sytest-blacklist +++ b/sytest-blacklist @@ -31,4 +31,7 @@ Alias creators can delete canonical alias with no ops # Blacklisted because we need to implement v2 invite endpoints for room versions # to be supported (currently fails with M_UNSUPPORTED_ROOM_VERSION) -Inbound federation rejects invites which are not signed by the sender \ No newline at end of file +Inbound federation rejects invites which are not signed by the sender + +# Blacklisted because we don't support ignores yet +Ignore invite in incremental sync diff --git a/sytest-whitelist b/sytest-whitelist index 38062bb5..97ded9bb 100644 --- a/sytest-whitelist +++ b/sytest-whitelist @@ -215,7 +215,6 @@ Guest users can sync from default guest_access rooms if joined Real non-joined users cannot room initalSync for non-world_readable rooms Push rules come down in an initial /sync Regular users can add and delete aliases in the default room configuration -Regular users can add and delete aliases when m.room.aliases is restricted GET /r0/capabilities is not public GET /joined_rooms lists newly-created room /joined_rooms returns only joined rooms @@ -225,10 +224,24 @@ Remote user can backfill in a room with version 1 POST /createRoom creates a room with the given version POST /createRoom rejects attempts to create rooms with numeric versions POST /createRoom rejects attempts to create rooms with unknown versions +Regular users can add and delete aliases when m.room.aliases is restricted User can create and send/receive messages in a room with version 2 local user can join room with version 2 remote user can join room with version 2 User can invite local user to room with version 2 Remote user can backfill in a room with version 2 Inbound federation accepts attempts to join v2 rooms from servers with support -Outbound federation can send invites via v2 API \ No newline at end of file +Outbound federation can send invites via v2 API +User can create and send/receive messages in a room with version 3 +local user can join room with version 3 +Remote user can backfill in a room with version 3 +User can create and send/receive messages in a room with version 4 +local user can join room with version 4 +remote user can join room with version 3 +remote user can join room with version 4 +Remote user can backfill in a room with version 4 +# We don't support ignores yet, so ignore this for now - ha ha. +# Ignore invite in incremental sync +Outbound federation can send invites via v2 API +User can invite local user to room with version 3 +User can invite local user to room with version 4