From c20109a57357fbb2cd0857485e2cca4a58c37d1b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 5 Feb 2020 18:06:39 +0000 Subject: [PATCH] Implement room version capabilities in CS API (#866) * Add wiring for querying the roomserver for the default room version * Try to implement /capabilities for room versions * Update copyright notices * Update sytests, add /capabilities endpoint into CS API * Update sytest-whitelist * Add GetDefaultRoomVersion * Fix cases where state package was shadowed * Fix version formatting * Update Dockerfile to Go 1.13.6 * oh yes types I remember * And fix the default too --- clientapi/routing/capabilities.go | 51 ++++++++++++++++++++++++ clientapi/routing/routing.go | 6 +++ docker/Dockerfile | 2 +- roomserver/api/query.go | 34 ++++++++++++++++ roomserver/input/events.go | 6 ++- roomserver/input/latest_events.go | 10 +++-- roomserver/query/query.go | 57 ++++++++++++++++++++++----- roomserver/state/database/database.go | 16 ++++++++ roomserver/state/state.go | 16 ++++++++ roomserver/state/v1/state_test.go | 2 + roomserver/storage/storage.go | 1 - roomserver/version/version.go | 18 +++++++++ sytest-blacklist | 3 ++ sytest-whitelist | 5 ++- 14 files changed, 208 insertions(+), 19 deletions(-) create mode 100644 clientapi/routing/capabilities.go diff --git a/clientapi/routing/capabilities.go b/clientapi/routing/capabilities.go new file mode 100644 index 00000000..c8743386 --- /dev/null +++ b/clientapi/routing/capabilities.go @@ -0,0 +1,51 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package routing + +import ( + "net/http" + + "github.com/matrix-org/dendrite/clientapi/httputil" + roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" + + "github.com/matrix-org/util" +) + +// SendMembership implements PUT /rooms/{roomID}/(join|kick|ban|unban|leave|invite) +// by building a m.room.member event then sending it to the room server +func GetCapabilities( + req *http.Request, queryAPI roomserverAPI.RoomserverQueryAPI, +) util.JSONResponse { + roomVersionsQueryReq := roomserverAPI.QueryRoomVersionCapabilitiesRequest{} + var roomVersionsQueryRes roomserverAPI.QueryRoomVersionCapabilitiesResponse + if err := queryAPI.QueryRoomVersionCapabilities( + req.Context(), + &roomVersionsQueryReq, + &roomVersionsQueryRes, + ); err != nil { + return httputil.LogThenError(req, err) + } + + response := map[string]interface{}{ + "capabilities": map[string]interface{}{ + "m.room_versions": roomVersionsQueryRes, + }, + } + + return util.JSONResponse{ + Code: http.StatusOK, + JSON: response, + } +} diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index 5c98cd0d..bd326900 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -551,4 +551,10 @@ func Setup( return DeleteTag(req, accountDB, device, vars["userId"], vars["roomId"], vars["tag"], syncProducer) }), ).Methods(http.MethodDelete, http.MethodOptions) + + r0mux.Handle("/capabilities", + common.MakeAuthAPI("capabilities", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse { + return GetCapabilities(req, queryAPI) + }), + ).Methods(http.MethodGet) } diff --git a/docker/Dockerfile b/docker/Dockerfile index d8f9de42..c88b7761 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/golang:1.12.5-alpine3.9 +FROM docker.io/golang:1.13.6-alpine RUN mkdir /build diff --git a/roomserver/api/query.go b/roomserver/api/query.go index b3fa0184..e1850e72 100644 --- a/roomserver/api/query.go +++ b/roomserver/api/query.go @@ -1,4 +1,6 @@ // Copyright 2017 Vector Creations Ltd +// Copyright 2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -244,6 +246,15 @@ type QueryServersInRoomAtEventResponse struct { Servers []gomatrixserverlib.ServerName `json:"servers"` } +// QueryRoomVersionCapabilities asks for the default room version +type QueryRoomVersionCapabilitiesRequest struct{} + +// QueryRoomVersionCapabilitiesResponse is a response to QueryServersInRoomAtEventResponse +type QueryRoomVersionCapabilitiesResponse struct { + DefaultRoomVersion string `json:"default"` + AvailableRoomVersions map[string]string `json:"available"` +} + // RoomserverQueryAPI is used to query information from the room server. type RoomserverQueryAPI interface { // Query the latest events and state for a room from the room server. @@ -323,6 +334,13 @@ type RoomserverQueryAPI interface { request *QueryServersInRoomAtEventRequest, response *QueryServersInRoomAtEventResponse, ) error + + // Asks for the default room version as preferred by the server. + QueryRoomVersionCapabilities( + ctx context.Context, + request *QueryRoomVersionCapabilitiesRequest, + response *QueryRoomVersionCapabilitiesResponse, + ) error } // RoomserverQueryLatestEventsAndStatePath is the HTTP path for the QueryLatestEventsAndState API. @@ -358,6 +376,9 @@ const RoomserverQueryBackfillPath = "/api/roomserver/queryBackfill" // RoomserverQueryServersInRoomAtEventPath is the HTTP path for the QueryServersInRoomAtEvent API const RoomserverQueryServersInRoomAtEventPath = "/api/roomserver/queryServersInRoomAtEvents" +// RoomserverQueryRoomVersionCapabilitiesPath is the HTTP path for the QueryRoomVersionCapabilities API +const RoomserverQueryRoomVersionCapabilitiesPath = "/api/roomserver/queryRoomVersionCapabilities" + // NewRoomserverQueryAPIHTTP creates a RoomserverQueryAPI implemented by talking to a HTTP POST API. // If httpClient is nil then it uses the http.DefaultClient func NewRoomserverQueryAPIHTTP(roomserverURL string, httpClient *http.Client) RoomserverQueryAPI { @@ -514,3 +535,16 @@ func (h *httpRoomserverQueryAPI) QueryServersInRoomAtEvent( apiURL := h.roomserverURL + RoomserverQueryServersInRoomAtEventPath return commonHTTP.PostJSON(ctx, span, h.httpClient, apiURL, request, response) } + +// QueryServersInRoomAtEvent implements RoomServerQueryAPI +func (h *httpRoomserverQueryAPI) QueryRoomVersionCapabilities( + ctx context.Context, + request *QueryRoomVersionCapabilitiesRequest, + response *QueryRoomVersionCapabilitiesResponse, +) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "QueryRoomVersionCapabilities") + defer span.Finish() + + apiURL := h.roomserverURL + RoomserverQueryRoomVersionCapabilitiesPath + return commonHTTP.PostJSON(ctx, span, h.httpClient, apiURL, request, response) +} diff --git a/roomserver/input/events.go b/roomserver/input/events.go index 10ccb648..03023a4a 100644 --- a/roomserver/input/events.go +++ b/roomserver/input/events.go @@ -1,4 +1,6 @@ // Copyright 2017 Vector Creations Ltd +// Copyright 2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -151,7 +153,7 @@ func calculateAndSetState( event gomatrixserverlib.Event, ) error { // TODO: get the correct room version - state, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, db) + roomState, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, db) if err != nil { return err } @@ -169,7 +171,7 @@ func calculateAndSetState( } } else { // We haven't been told what the state at the event is so we need to calculate it from the prev_events - if stateAtEvent.BeforeStateSnapshotNID, err = state.CalculateAndStoreStateBeforeEvent(ctx, event, roomNID); err != nil { + if stateAtEvent.BeforeStateSnapshotNID, err = roomState.CalculateAndStoreStateBeforeEvent(ctx, event, roomNID); err != nil { return err } } diff --git a/roomserver/input/latest_events.go b/roomserver/input/latest_events.go index 760677db..7e03d544 100644 --- a/roomserver/input/latest_events.go +++ b/roomserver/input/latest_events.go @@ -1,4 +1,6 @@ // Copyright 2017 Vector Creations Ltd +// Copyright 2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -172,7 +174,7 @@ func (u *latestEventsUpdater) doUpdateLatestEvents() error { func (u *latestEventsUpdater) latestState() error { var err error // TODO: get the correct room version - state, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, u.db) + roomState, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, u.db) if err != nil { return err } @@ -181,21 +183,21 @@ func (u *latestEventsUpdater) latestState() error { for i := range u.latest { latestStateAtEvents[i] = u.latest[i].StateAtEvent } - u.newStateNID, err = state.CalculateAndStoreStateAfterEvents( + u.newStateNID, err = roomState.CalculateAndStoreStateAfterEvents( u.ctx, u.roomNID, latestStateAtEvents, ) if err != nil { return err } - u.removed, u.added, err = state.DifferenceBetweeenStateSnapshots( + u.removed, u.added, err = roomState.DifferenceBetweeenStateSnapshots( u.ctx, u.oldStateNID, u.newStateNID, ) if err != nil { return err } - u.stateBeforeEventRemoves, u.stateBeforeEventAdds, err = state.DifferenceBetweeenStateSnapshots( + u.stateBeforeEventRemoves, u.stateBeforeEventAdds, err = roomState.DifferenceBetweeenStateSnapshots( u.ctx, u.newStateNID, u.stateAtEvent.BeforeStateSnapshotNID, ) return err diff --git a/roomserver/query/query.go b/roomserver/query/query.go index d318fc00..f138686b 100644 --- a/roomserver/query/query.go +++ b/roomserver/query/query.go @@ -1,4 +1,6 @@ // Copyright 2017 Vector Creations Ltd +// Copyright 2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,6 +20,7 @@ import ( "context" "encoding/json" "net/http" + "strconv" "github.com/matrix-org/dendrite/common" "github.com/matrix-org/dendrite/roomserver/api" @@ -25,6 +28,7 @@ import ( "github.com/matrix-org/dendrite/roomserver/state" "github.com/matrix-org/dendrite/roomserver/state/database" "github.com/matrix-org/dendrite/roomserver/types" + "github.com/matrix-org/dendrite/roomserver/version" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" ) @@ -100,7 +104,7 @@ func (r *RoomserverQueryAPI) QueryLatestEventsAndState( response *api.QueryLatestEventsAndStateResponse, ) error { // TODO: get the correct room version - state, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, r.DB) + roomState, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, r.DB) if err != nil { return err } @@ -121,7 +125,7 @@ func (r *RoomserverQueryAPI) QueryLatestEventsAndState( } // Look up the currrent state for the requested tuples. - stateEntries, err := state.LoadStateAtSnapshotForStringTuples( + stateEntries, err := roomState.LoadStateAtSnapshotForStringTuples( ctx, currentStateSnapshotNID, request.StateToFetch, ) if err != nil { @@ -144,7 +148,7 @@ func (r *RoomserverQueryAPI) QueryStateAfterEvents( response *api.QueryStateAfterEventsResponse, ) error { // TODO: get the correct room version - state, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, r.DB) + roomState, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, r.DB) if err != nil { return err } @@ -170,7 +174,7 @@ func (r *RoomserverQueryAPI) QueryStateAfterEvents( response.PrevEventsExist = true // Look up the currrent state for the requested tuples. - stateEntries, err := state.LoadStateAfterEventsForStringTuples( + stateEntries, err := roomState.LoadStateAfterEventsForStringTuples( ctx, prevStates, request.StateToFetch, ) if err != nil { @@ -327,7 +331,7 @@ func (r *RoomserverQueryAPI) getMembershipsBeforeEventNID( ctx context.Context, eventNID types.EventNID, joinedOnly bool, ) ([]types.Event, error) { // TODO: get the correct room version - state, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, r.DB) + roomState, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, r.DB) if err != nil { return []types.Event{}, err } @@ -345,7 +349,7 @@ func (r *RoomserverQueryAPI) getMembershipsBeforeEventNID( } // Fetch the state as it was when this event was fired - stateEntries, err := state.LoadCombinedStateAfterEvents(ctx, prevState) + stateEntries, err := roomState.LoadCombinedStateAfterEvents(ctx, prevState) if err != nil { return nil, err } @@ -433,12 +437,12 @@ func (r *RoomserverQueryAPI) checkServerAllowedToSeeEvent( ctx context.Context, eventID string, serverName gomatrixserverlib.ServerName, ) (bool, error) { // TODO: get the correct room version - state, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, r.DB) + roomState, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, r.DB) if err != nil { return false, err } - stateEntries, err := state.LoadStateAtEvent(ctx, eventID) + stateEntries, err := roomState.LoadStateAtEvent(ctx, eventID) if err != nil { return false, err } @@ -593,7 +597,7 @@ func (r *RoomserverQueryAPI) QueryStateAndAuthChain( response *api.QueryStateAndAuthChainResponse, ) error { // TODO: get the correct room version - state, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, r.DB) + roomState, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, r.DB) if err != nil { return err } @@ -620,7 +624,7 @@ func (r *RoomserverQueryAPI) QueryStateAndAuthChain( response.PrevEventsExist = true // Look up the currrent state for the requested tuples. - stateEntries, err := state.LoadCombinedStateAfterEvents( + stateEntries, err := roomState.LoadCombinedStateAfterEvents( ctx, prevStates, ) if err != nil { @@ -723,6 +727,25 @@ func (r *RoomserverQueryAPI) QueryServersInRoomAtEvent( return nil } +// QueryRoomVersionCapabilities implements api.RoomserverQueryAPI +func (r *RoomserverQueryAPI) QueryRoomVersionCapabilities( + ctx context.Context, + request *api.QueryRoomVersionCapabilitiesRequest, + response *api.QueryRoomVersionCapabilitiesResponse, +) error { + response.DefaultRoomVersion = strconv.Itoa(int(version.GetDefaultRoomVersion())) + response.AvailableRoomVersions = make(map[string]string) + for v, desc := range version.GetSupportedRoomVersions() { + sv := strconv.Itoa(int(v)) + if desc.Stable { + response.AvailableRoomVersions[sv] = "stable" + } else { + response.AvailableRoomVersions[sv] = "unstable" + } + } + return nil +} + // SetupHTTP adds the RoomserverQueryAPI handlers to the http.ServeMux. // nolint: gocyclo func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) { @@ -880,4 +903,18 @@ func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) { return util.JSONResponse{Code: http.StatusOK, JSON: &response} }), ) + servMux.Handle( + api.RoomserverQueryRoomVersionCapabilitiesPath, + common.MakeInternalAPI("QueryRoomVersionCapabilities", func(req *http.Request) util.JSONResponse { + var request api.QueryRoomVersionCapabilitiesRequest + var response api.QueryRoomVersionCapabilitiesResponse + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.ErrorResponse(err) + } + if err := r.QueryRoomVersionCapabilities(req.Context(), &request, &response); err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) } diff --git a/roomserver/state/database/database.go b/roomserver/state/database/database.go index 546f06e8..ede6c5ec 100644 --- a/roomserver/state/database/database.go +++ b/roomserver/state/database/database.go @@ -1,3 +1,19 @@ +// Copyright 2017 Vector Creations Ltd +// Copyright 2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. +// +// 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 database import ( diff --git a/roomserver/state/state.go b/roomserver/state/state.go index bbc27ad8..687a120e 100644 --- a/roomserver/state/state.go +++ b/roomserver/state/state.go @@ -1,3 +1,19 @@ +// Copyright 2017 Vector Creations Ltd +// Copyright 2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. +// +// 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 state import ( diff --git a/roomserver/state/v1/state_test.go b/roomserver/state/v1/state_test.go index 414ca2a1..4dc7e52e 100644 --- a/roomserver/state/v1/state_test.go +++ b/roomserver/state/v1/state_test.go @@ -1,4 +1,6 @@ // Copyright 2017 Vector Creations Ltd +// Copyright 2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/roomserver/storage/storage.go b/roomserver/storage/storage.go index 67efe656..90841168 100644 --- a/roomserver/storage/storage.go +++ b/roomserver/storage/storage.go @@ -55,7 +55,6 @@ type Database interface { GetMembershipEventNIDsForRoom(ctx context.Context, roomNID types.RoomNID, joinOnly bool) ([]types.EventNID, error) EventsFromIDs(ctx context.Context, eventIDs []string) ([]types.Event, error) GetRoomVersionForRoom(ctx context.Context, roomNID types.RoomNID) (int64, error) - //GetRoomVersionForEvent(ctx context.Context, eventNID types.EventNID) int64 } // NewPublicRoomsServerDatabase opens a database connection. diff --git a/roomserver/version/version.go b/roomserver/version/version.go index 74d04d9b..0943e384 100644 --- a/roomserver/version/version.go +++ b/roomserver/version/version.go @@ -1,3 +1,17 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// 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 version import ( @@ -69,6 +83,10 @@ var roomVersions = map[RoomVersionID]RoomVersionDescription{ }, } +func GetDefaultRoomVersion() RoomVersionID { + return RoomVersionV1 +} + func GetRoomVersions() map[RoomVersionID]RoomVersionDescription { return roomVersions } diff --git a/sytest-blacklist b/sytest-blacklist index 4c36e477..5ddc18f6 100644 --- a/sytest-blacklist +++ b/sytest-blacklist @@ -19,3 +19,6 @@ Alias creators can delete alias with no ops # Blacklisted because matrix-org/dendrite#847 might have broken it but we're not # really sure and we need it pretty badly anyway Real non-joined users can get individual state for world_readable rooms after leaving + +# Blacklisted until matrix-org/dendrite#862 is reverted due to Riot bug +Latest account data appears in v2 /sync diff --git a/sytest-whitelist b/sytest-whitelist index 0600e2e5..efc97b5e 100644 --- a/sytest-whitelist +++ b/sytest-whitelist @@ -112,7 +112,7 @@ User can invite local user to room with version 4 Should reject keys claiming to belong to a different user Can add account data Can add account data to room -Latest account data appears in v2 /sync +#Latest account data appears in v2 /sync New account data appears in incremental v2 /sync Checking local federation server Inbound federation can query profile data @@ -227,3 +227,6 @@ Guest users can sync from world_readable guest_access rooms if joined 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