aboutsummaryrefslogtreecommitdiff
path: root/clientapi
diff options
context:
space:
mode:
authorKegsay <kegan@matrix.org>2020-07-02 15:41:18 +0100
committerGitHub <noreply@github.com>2020-07-02 15:41:18 +0100
commit4c1e6597c0ea82f5390b73f35036db58e65542cc (patch)
tree641e916f8b4f753f5d45ec674f3512fdb9fbb74b /clientapi
parent55bc82c439057f379361871c863aa9611d70fce2 (diff)
Replace publicroomsapi with a combination of clientapi/roomserver/currentstateserver (#1174)
* Use content_value instead of membership * Fix build * Replace publicroomsapi with a combination of clientapi/roomserver/currentstateserver - All public rooms paths are now handled by clientapi - Requests to (un)publish rooms are sent to the roomserver via `PerformPublish` which are stored in a new `published_table.go` - Requests for public rooms are handled in clientapi by: * Fetch all room IDs which are published using `QueryPublishedRooms` on the roomserver. * Apply pagination parameters to the slice. * Do a `QueryBulkStateContent` request to the currentstateserver to pull out required state event *content* (not entire events). * Aggregate and return the chunk. Mostly but not fully implemented (DB queries on currentstateserver are missing) * Fix pq query * Make postgres work * Make sqlite work * Fix tests * Unbreak pagination tests * Linting
Diffstat (limited to 'clientapi')
-rw-r--r--clientapi/routing/createroom.go13
-rw-r--r--clientapi/routing/directory.go90
-rw-r--r--clientapi/routing/directory_public.go373
-rw-r--r--clientapi/routing/directory_public_test.go48
-rw-r--r--clientapi/routing/membership.go33
-rw-r--r--clientapi/routing/routing.go31
6 files changed, 586 insertions, 2 deletions
diff --git a/clientapi/routing/createroom.go b/clientapi/routing/createroom.go
index 42e1895c..b6a5d122 100644
--- a/clientapi/routing/createroom.go
+++ b/clientapi/routing/createroom.go
@@ -410,6 +410,19 @@ func createRoom(
}
}
+ if r.Visibility == "public" {
+ // expose this room in the published room list
+ var pubRes roomserverAPI.PerformPublishResponse
+ rsAPI.PerformPublish(req.Context(), &roomserverAPI.PerformPublishRequest{
+ RoomID: roomID,
+ Visibility: "public",
+ }, &pubRes)
+ if pubRes.Error != nil {
+ // treat as non-fatal since the room is already made by this point
+ util.GetLogger(req.Context()).WithError(pubRes.Error).Error("failed to visibility:public")
+ }
+ }
+
response := createRoomResponse{
RoomID: roomID,
RoomAlias: roomAlias,
diff --git a/clientapi/routing/directory.go b/clientapi/routing/directory.go
index 0dc4d560..0f78f4a2 100644
--- a/clientapi/routing/directory.go
+++ b/clientapi/routing/directory.go
@@ -1,4 +1,4 @@
-// Copyright 2017 Vector Creations Ltd
+// 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.
@@ -20,10 +20,12 @@ import (
"github.com/matrix-org/dendrite/clientapi/httputil"
"github.com/matrix-org/dendrite/clientapi/jsonerror"
+ currentstateAPI "github.com/matrix-org/dendrite/currentstateserver/api"
federationSenderAPI "github.com/matrix-org/dendrite/federationsender/api"
"github.com/matrix-org/dendrite/internal/config"
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/userapi/api"
+ userapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util"
)
@@ -232,3 +234,89 @@ func RemoveLocalAlias(
JSON: struct{}{},
}
}
+
+type roomVisibility struct {
+ Visibility string `json:"visibility"`
+}
+
+// GetVisibility implements GET /directory/list/room/{roomID}
+func GetVisibility(
+ req *http.Request, rsAPI roomserverAPI.RoomserverInternalAPI,
+ roomID string,
+) util.JSONResponse {
+ var res roomserverAPI.QueryPublishedRoomsResponse
+ err := rsAPI.QueryPublishedRooms(req.Context(), &roomserverAPI.QueryPublishedRoomsRequest{
+ RoomID: roomID,
+ }, &res)
+ if err != nil {
+ util.GetLogger(req.Context()).WithError(err).Error("QueryPublishedRooms failed")
+ return jsonerror.InternalServerError()
+ }
+
+ var v roomVisibility
+ if len(res.RoomIDs) == 1 {
+ v.Visibility = gomatrixserverlib.Public
+ } else {
+ v.Visibility = "private"
+ }
+
+ return util.JSONResponse{
+ Code: http.StatusOK,
+ JSON: v,
+ }
+}
+
+// SetVisibility implements PUT /directory/list/room/{roomID}
+// TODO: Allow admin users to edit the room visibility
+func SetVisibility(
+ req *http.Request, stateAPI currentstateAPI.CurrentStateInternalAPI, rsAPI roomserverAPI.RoomserverInternalAPI, dev *userapi.Device,
+ roomID string,
+) util.JSONResponse {
+ resErr := checkMemberInRoom(req.Context(), stateAPI, dev.UserID, roomID)
+ if resErr != nil {
+ return *resErr
+ }
+
+ queryEventsReq := roomserverAPI.QueryLatestEventsAndStateRequest{
+ RoomID: roomID,
+ StateToFetch: []gomatrixserverlib.StateKeyTuple{{
+ EventType: gomatrixserverlib.MRoomPowerLevels,
+ StateKey: "",
+ }},
+ }
+ var queryEventsRes roomserverAPI.QueryLatestEventsAndStateResponse
+ err := rsAPI.QueryLatestEventsAndState(req.Context(), &queryEventsReq, &queryEventsRes)
+ if err != nil || len(queryEventsRes.StateEvents) == 0 {
+ util.GetLogger(req.Context()).WithError(err).Error("could not query events from room")
+ return jsonerror.InternalServerError()
+ }
+
+ // NOTSPEC: Check if the user's power is greater than power required to change m.room.aliases event
+ power, _ := gomatrixserverlib.NewPowerLevelContentFromEvent(queryEventsRes.StateEvents[0].Event)
+ if power.UserLevel(dev.UserID) < power.EventLevel(gomatrixserverlib.MRoomAliases, true) {
+ return util.JSONResponse{
+ Code: http.StatusForbidden,
+ JSON: jsonerror.Forbidden("userID doesn't have power level to change visibility"),
+ }
+ }
+
+ var v roomVisibility
+ if reqErr := httputil.UnmarshalJSONRequest(req, &v); reqErr != nil {
+ return *reqErr
+ }
+
+ var publishRes roomserverAPI.PerformPublishResponse
+ rsAPI.PerformPublish(req.Context(), &roomserverAPI.PerformPublishRequest{
+ RoomID: roomID,
+ Visibility: v.Visibility,
+ }, &publishRes)
+ if publishRes.Error != nil {
+ util.GetLogger(req.Context()).WithError(publishRes.Error).Error("PerformPublish failed")
+ return publishRes.Error.JSONResponse()
+ }
+
+ return util.JSONResponse{
+ Code: http.StatusOK,
+ JSON: struct{}{},
+ }
+}
diff --git a/clientapi/routing/directory_public.go b/clientapi/routing/directory_public.go
new file mode 100644
index 00000000..6d0db579
--- /dev/null
+++ b/clientapi/routing/directory_public.go
@@ -0,0 +1,373 @@
+// 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 (
+ "context"
+ "math/rand"
+ "net/http"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/matrix-org/dendrite/clientapi/httputil"
+ "github.com/matrix-org/dendrite/clientapi/jsonerror"
+ currentstateAPI "github.com/matrix-org/dendrite/currentstateserver/api"
+ "github.com/matrix-org/dendrite/publicroomsapi/types"
+ roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
+ "github.com/matrix-org/gomatrixserverlib"
+ "github.com/matrix-org/util"
+)
+
+type PublicRoomReq struct {
+ Since string `json:"since,omitempty"`
+ Limit int16 `json:"limit,omitempty"`
+ Filter filter `json:"filter,omitempty"`
+}
+
+type filter struct {
+ SearchTerms string `json:"generic_search_term,omitempty"`
+}
+
+// GetPostPublicRooms implements GET and POST /publicRooms
+func GetPostPublicRooms(
+ req *http.Request, rsAPI roomserverAPI.RoomserverInternalAPI, stateAPI currentstateAPI.CurrentStateInternalAPI,
+) util.JSONResponse {
+ var request PublicRoomReq
+ if fillErr := fillPublicRoomsReq(req, &request); fillErr != nil {
+ return *fillErr
+ }
+ response, err := publicRooms(req.Context(), request, rsAPI, stateAPI)
+ if err != nil {
+ return jsonerror.InternalServerError()
+ }
+ return util.JSONResponse{
+ Code: http.StatusOK,
+ JSON: response,
+ }
+}
+
+// GetPostPublicRoomsWithExternal is the same as GetPostPublicRooms but also mixes in public rooms from the provider supplied.
+func GetPostPublicRoomsWithExternal(
+ req *http.Request, rsAPI roomserverAPI.RoomserverInternalAPI, stateAPI currentstateAPI.CurrentStateInternalAPI,
+ fedClient *gomatrixserverlib.FederationClient, extRoomsProvider types.ExternalPublicRoomsProvider,
+) util.JSONResponse {
+ var request PublicRoomReq
+ if fillErr := fillPublicRoomsReq(req, &request); fillErr != nil {
+ return *fillErr
+ }
+ response, err := publicRooms(req.Context(), request, rsAPI, stateAPI)
+ if err != nil {
+ return jsonerror.InternalServerError()
+ }
+
+ if request.Since != "" {
+ // TODO: handle pagination tokens sensibly rather than ignoring them.
+ // ignore paginated requests since we don't handle them yet over federation.
+ // Only the initial request will contain federated rooms.
+ return util.JSONResponse{
+ Code: http.StatusOK,
+ JSON: response,
+ }
+ }
+
+ // If we have already hit the limit on the number of rooms, bail.
+ var limit int
+ if request.Limit > 0 {
+ limit = int(request.Limit) - len(response.Chunk)
+ if limit <= 0 {
+ return util.JSONResponse{
+ Code: http.StatusOK,
+ JSON: response,
+ }
+ }
+ }
+
+ // downcasting `limit` is safe as we know it isn't bigger than request.Limit which is int16
+ fedRooms := bulkFetchPublicRoomsFromServers(req.Context(), fedClient, extRoomsProvider.Homeservers(), int16(limit))
+ response.Chunk = append(response.Chunk, fedRooms...)
+
+ // de-duplicate rooms with the same room ID. We can join the room via any of these aliases as we know these servers
+ // are alive and well, so we arbitrarily pick one (purposefully shuffling them to spread the load a bit)
+ var publicRooms []gomatrixserverlib.PublicRoom
+ haveRoomIDs := make(map[string]bool)
+ rand.Shuffle(len(response.Chunk), func(i, j int) {
+ response.Chunk[i], response.Chunk[j] = response.Chunk[j], response.Chunk[i]
+ })
+ for _, r := range response.Chunk {
+ if haveRoomIDs[r.RoomID] {
+ continue
+ }
+ haveRoomIDs[r.RoomID] = true
+ publicRooms = append(publicRooms, r)
+ }
+ // sort by member count
+ sort.SliceStable(publicRooms, func(i, j int) bool {
+ return publicRooms[i].JoinedMembersCount > publicRooms[j].JoinedMembersCount
+ })
+
+ response.Chunk = publicRooms
+
+ return util.JSONResponse{
+ Code: http.StatusOK,
+ JSON: response,
+ }
+}
+
+// bulkFetchPublicRoomsFromServers fetches public rooms from the list of homeservers.
+// Returns a list of public rooms up to the limit specified.
+func bulkFetchPublicRoomsFromServers(
+ ctx context.Context, fedClient *gomatrixserverlib.FederationClient, homeservers []string, limit int16,
+) (publicRooms []gomatrixserverlib.PublicRoom) {
+ // follow pipeline semantics, see https://blog.golang.org/pipelines for more info.
+ // goroutines send rooms to this channel
+ roomCh := make(chan gomatrixserverlib.PublicRoom, int(limit))
+ // signalling channel to tell goroutines to stop sending rooms and quit
+ done := make(chan bool)
+ // signalling to say when we can close the room channel
+ var wg sync.WaitGroup
+ wg.Add(len(homeservers))
+ // concurrently query for public rooms
+ for _, hs := range homeservers {
+ go func(homeserverDomain string) {
+ defer wg.Done()
+ util.GetLogger(ctx).WithField("hs", homeserverDomain).Info("Querying HS for public rooms")
+ fres, err := fedClient.GetPublicRooms(ctx, gomatrixserverlib.ServerName(homeserverDomain), int(limit), "", false, "")
+ if err != nil {
+ util.GetLogger(ctx).WithError(err).WithField("hs", homeserverDomain).Warn(
+ "bulkFetchPublicRoomsFromServers: failed to query hs",
+ )
+ return
+ }
+ for _, room := range fres.Chunk {
+ // atomically send a room or stop
+ select {
+ case roomCh <- room:
+ case <-done:
+ util.GetLogger(ctx).WithError(err).WithField("hs", homeserverDomain).Info("Interrupted whilst sending rooms")
+ return
+ }
+ }
+ }(hs)
+ }
+
+ // Close the room channel when the goroutines have quit so we don't leak, but don't let it stop the in-flight request.
+ // This also allows the request to fail fast if all HSes experience errors as it will cause the room channel to be
+ // closed.
+ go func() {
+ wg.Wait()
+ util.GetLogger(ctx).Info("Cleaning up resources")
+ close(roomCh)
+ }()
+
+ // fan-in results with timeout. We stop when we reach the limit.
+FanIn:
+ for len(publicRooms) < int(limit) || limit == 0 {
+ // add a room or timeout
+ select {
+ case room, ok := <-roomCh:
+ if !ok {
+ util.GetLogger(ctx).Info("All homeservers have been queried, returning results.")
+ break FanIn
+ }
+ publicRooms = append(publicRooms, room)
+ case <-time.After(15 * time.Second): // we've waited long enough, let's tell the client what we got.
+ util.GetLogger(ctx).Info("Waited 15s for federated public rooms, returning early")
+ break FanIn
+ case <-ctx.Done(): // the client hung up on us, let's stop.
+ util.GetLogger(ctx).Info("Client hung up, returning early")
+ break FanIn
+ }
+ }
+ // tell goroutines to stop
+ close(done)
+
+ return publicRooms
+}
+
+func publicRooms(ctx context.Context, request PublicRoomReq, rsAPI roomserverAPI.RoomserverInternalAPI,
+ stateAPI currentstateAPI.CurrentStateInternalAPI) (*gomatrixserverlib.RespPublicRooms, error) {
+
+ var response gomatrixserverlib.RespPublicRooms
+ var limit int16
+ var offset int64
+ limit = request.Limit
+ if limit == 0 {
+ limit = 50
+ }
+ offset, err := strconv.ParseInt(request.Since, 10, 64)
+ // ParseInt returns 0 and an error when trying to parse an empty string
+ // In that case, we want to assign 0 so we ignore the error
+ if err != nil && len(request.Since) > 0 {
+ util.GetLogger(ctx).WithError(err).Error("strconv.ParseInt failed")
+ return nil, err
+ }
+
+ var queryRes roomserverAPI.QueryPublishedRoomsResponse
+ err = rsAPI.QueryPublishedRooms(ctx, &roomserverAPI.QueryPublishedRoomsRequest{}, &queryRes)
+ if err != nil {
+ util.GetLogger(ctx).WithError(err).Error("QueryPublishedRooms failed")
+ return nil, err
+ }
+ response.TotalRoomCountEstimate = len(queryRes.RoomIDs)
+
+ roomIDs, prev, next := sliceInto(queryRes.RoomIDs, offset, limit)
+ if prev >= 0 {
+ response.PrevBatch = "T" + strconv.Itoa(prev)
+ }
+ if next >= 0 {
+ response.NextBatch = "T" + strconv.Itoa(next)
+ }
+ response.Chunk, err = fillInRooms(ctx, roomIDs, stateAPI)
+ return &response, err
+}
+
+// fillPublicRoomsReq fills the Limit, Since and Filter attributes of a GET or POST request
+// on /publicRooms by parsing the incoming HTTP request
+// Filter is only filled for POST requests
+func fillPublicRoomsReq(httpReq *http.Request, request *PublicRoomReq) *util.JSONResponse {
+ if httpReq.Method != "GET" && httpReq.Method != "POST" {
+ return &util.JSONResponse{
+ Code: http.StatusMethodNotAllowed,
+ JSON: jsonerror.NotFound("Bad method"),
+ }
+ }
+ if httpReq.Method == "GET" {
+ limit, err := strconv.Atoi(httpReq.FormValue("limit"))
+ // Atoi returns 0 and an error when trying to parse an empty string
+ // In that case, we want to assign 0 so we ignore the error
+ if err != nil && len(httpReq.FormValue("limit")) > 0 {
+ util.GetLogger(httpReq.Context()).WithError(err).Error("strconv.Atoi failed")
+ return &util.JSONResponse{
+ Code: 400,
+ JSON: jsonerror.BadJSON("limit param is not a number"),
+ }
+ }
+ request.Limit = int16(limit)
+ request.Since = httpReq.FormValue("since")
+ } else {
+ resErr := httputil.UnmarshalJSONRequest(httpReq, request)
+ if resErr != nil {
+ return resErr
+ }
+ }
+
+ // strip the 'T' which is only required because when sytest does pagination tests it stops
+ // iterating when !prev_batch which then fails if prev_batch==0, so add arbitrary text to
+ // make it truthy not falsey.
+ request.Since = strings.TrimPrefix(request.Since, "T")
+ return nil
+}
+
+// due to lots of switches
+// nolint:gocyclo
+func fillInRooms(ctx context.Context, roomIDs []string, stateAPI currentstateAPI.CurrentStateInternalAPI) ([]gomatrixserverlib.PublicRoom, error) {
+ avatarTuple := gomatrixserverlib.StateKeyTuple{EventType: "m.room.avatar", StateKey: ""}
+ nameTuple := gomatrixserverlib.StateKeyTuple{EventType: "m.room.name", StateKey: ""}
+ canonicalTuple := gomatrixserverlib.StateKeyTuple{EventType: gomatrixserverlib.MRoomCanonicalAlias, StateKey: ""}
+ topicTuple := gomatrixserverlib.StateKeyTuple{EventType: "m.room.topic", StateKey: ""}
+ guestTuple := gomatrixserverlib.StateKeyTuple{EventType: "m.room.guest_access", StateKey: ""}
+ visibilityTuple := gomatrixserverlib.StateKeyTuple{EventType: gomatrixserverlib.MRoomHistoryVisibility, StateKey: ""}
+ joinRuleTuple := gomatrixserverlib.StateKeyTuple{EventType: gomatrixserverlib.MRoomJoinRules, StateKey: ""}
+
+ var stateRes currentstateAPI.QueryBulkStateContentResponse
+ err := stateAPI.QueryBulkStateContent(ctx, &currentstateAPI.QueryBulkStateContentRequest{
+ RoomIDs: roomIDs,
+ AllowWildcards: true,
+ StateTuples: []gomatrixserverlib.StateKeyTuple{
+ nameTuple, canonicalTuple, topicTuple, guestTuple, visibilityTuple, joinRuleTuple, avatarTuple,
+ {EventType: gomatrixserverlib.MRoomMember, StateKey: "*"},
+ },
+ }, &stateRes)
+ if err != nil {
+ util.GetLogger(ctx).WithError(err).Error("QueryBulkStateContent failed")
+ return nil, err
+ }
+ chunk := make([]gomatrixserverlib.PublicRoom, len(roomIDs))
+ i := 0
+ for roomID, data := range stateRes.Rooms {
+ pub := gomatrixserverlib.PublicRoom{
+ RoomID: roomID,
+ }
+ joinCount := 0
+ var joinRule, guestAccess string
+ for tuple, contentVal := range data {
+ if tuple.EventType == gomatrixserverlib.MRoomMember && contentVal == "join" {
+ joinCount++
+ continue
+ }
+ switch tuple {
+ case avatarTuple:
+ pub.AvatarURL = contentVal
+ case nameTuple:
+ pub.Name = contentVal
+ case topicTuple:
+ pub.Topic = contentVal
+ case canonicalTuple:
+ pub.CanonicalAlias = contentVal
+ case visibilityTuple:
+ pub.WorldReadable = contentVal == "world_readable"
+ // need both of these to determine whether guests can join
+ case joinRuleTuple:
+ joinRule = contentVal
+ case guestTuple:
+ guestAccess = contentVal
+ }
+ }
+ if joinRule == gomatrixserverlib.Public && guestAccess == "can_join" {
+ pub.GuestCanJoin = true
+ }
+ pub.JoinedMembersCount = joinCount
+ chunk[i] = pub
+ i++
+ }
+ return chunk, nil
+}
+
+// sliceInto returns a subslice of `slice` which honours the since/limit values given.
+//
+// 0 1 2 3 4 5 6 index
+// [A, B, C, D, E, F, G] slice
+//
+// limit=3 => A,B,C (prev='', next='3')
+// limit=3&since=3 => D,E,F (prev='0', next='6')
+// limit=3&since=6 => G (prev='3', next='')
+//
+// A value of '-1' for prev/next indicates no position.
+func sliceInto(slice []string, since int64, limit int16) (subset []string, prev, next int) {
+ prev = -1
+ next = -1
+
+ if since > 0 {
+ prev = int(since) - int(limit)
+ }
+ nextIndex := int(since) + int(limit)
+ if len(slice) > nextIndex { // there are more rooms ahead of us
+ next = nextIndex
+ }
+
+ // apply sanity caps
+ if since < 0 {
+ since = 0
+ }
+ if nextIndex > len(slice) {
+ nextIndex = len(slice)
+ }
+
+ subset = slice[since:nextIndex]
+ return
+}
diff --git a/clientapi/routing/directory_public_test.go b/clientapi/routing/directory_public_test.go
new file mode 100644
index 00000000..f2a1d551
--- /dev/null
+++ b/clientapi/routing/directory_public_test.go
@@ -0,0 +1,48 @@
+package routing
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestSliceInto(t *testing.T) {
+ slice := []string{"a", "b", "c", "d", "e", "f", "g"}
+ limit := int16(3)
+ testCases := []struct {
+ since int64
+ wantPrev int
+ wantNext int
+ wantSubset []string
+ }{
+ {
+ since: 0,
+ wantPrev: -1,
+ wantNext: 3,
+ wantSubset: slice[0:3],
+ },
+ {
+ since: 3,
+ wantPrev: 0,
+ wantNext: 6,
+ wantSubset: slice[3:6],
+ },
+ {
+ since: 6,
+ wantPrev: 3,
+ wantNext: -1,
+ wantSubset: slice[6:7],
+ },
+ }
+ for _, tc := range testCases {
+ subset, prev, next := sliceInto(slice, tc.since, limit)
+ if !reflect.DeepEqual(subset, tc.wantSubset) {
+ t.Errorf("returned subset is wrong, got %v want %v", subset, tc.wantSubset)
+ }
+ if prev != tc.wantPrev {
+ t.Errorf("returned prev is wrong, got %d want %d", prev, tc.wantPrev)
+ }
+ if next != tc.wantNext {
+ t.Errorf("returned next is wrong, got %d want %d", next, tc.wantNext)
+ }
+ }
+}
diff --git a/clientapi/routing/membership.go b/clientapi/routing/membership.go
index 1f316384..c2145159 100644
--- a/clientapi/routing/membership.go
+++ b/clientapi/routing/membership.go
@@ -25,6 +25,7 @@ import (
"github.com/matrix-org/dendrite/clientapi/httputil"
"github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/clientapi/threepid"
+ currentstateAPI "github.com/matrix-org/dendrite/currentstateserver/api"
"github.com/matrix-org/dendrite/internal/config"
"github.com/matrix-org/dendrite/internal/eventutil"
"github.com/matrix-org/dendrite/roomserver/api"
@@ -358,3 +359,35 @@ func checkAndProcessThreepid(
}
return
}
+
+func checkMemberInRoom(ctx context.Context, stateAPI currentstateAPI.CurrentStateInternalAPI, userID, roomID string) *util.JSONResponse {
+ tuple := gomatrixserverlib.StateKeyTuple{
+ EventType: gomatrixserverlib.MRoomMember,
+ StateKey: userID,
+ }
+ var membershipRes currentstateAPI.QueryCurrentStateResponse
+ err := stateAPI.QueryCurrentState(ctx, &currentstateAPI.QueryCurrentStateRequest{
+ RoomID: roomID,
+ StateTuples: []gomatrixserverlib.StateKeyTuple{tuple},
+ }, &membershipRes)
+ if err != nil {
+ util.GetLogger(ctx).WithError(err).Error("QueryCurrentState: could not query membership for user")
+ e := jsonerror.InternalServerError()
+ return &e
+ }
+ ev, ok := membershipRes.StateEvents[tuple]
+ if !ok {
+ return &util.JSONResponse{
+ Code: http.StatusForbidden,
+ JSON: jsonerror.Forbidden("user does not belong to room"),
+ }
+ }
+ membership, err := ev.Membership()
+ if err != nil || membership != "join" {
+ return &util.JSONResponse{
+ Code: http.StatusForbidden,
+ JSON: jsonerror.Forbidden("user does not belong to room"),
+ }
+ }
+ return nil
+}
diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go
index deaa7b32..57bb921d 100644
--- a/clientapi/routing/routing.go
+++ b/clientapi/routing/routing.go
@@ -1,4 +1,4 @@
-// Copyright 2017 Vector Creations Ltd
+// 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.
@@ -31,6 +31,7 @@ import (
"github.com/matrix-org/dendrite/internal/transactions"
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/userapi/api"
+ userapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/dendrite/userapi/storage/accounts"
"github.com/matrix-org/dendrite/userapi/storage/devices"
"github.com/matrix-org/gomatrixserverlib"
@@ -290,6 +291,34 @@ func Setup(
return RemoveLocalAlias(req, device, vars["roomAlias"], rsAPI)
}),
).Methods(http.MethodDelete, http.MethodOptions)
+ r0mux.Handle("/directory/list/room/{roomID}",
+ httputil.MakeExternalAPI("directory_list", func(req *http.Request) util.JSONResponse {
+ vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
+ if err != nil {
+ return util.ErrorResponse(err)
+ }
+ return GetVisibility(req, rsAPI, vars["roomID"])
+ }),
+ ).Methods(http.MethodGet, http.MethodOptions)
+ // TODO: Add AS support
+ r0mux.Handle("/directory/list/room/{roomID}",
+ httputil.MakeAuthAPI("directory_list", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
+ vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
+ if err != nil {
+ return util.ErrorResponse(err)
+ }
+ return SetVisibility(req, stateAPI, rsAPI, device, vars["roomID"])
+ }),
+ ).Methods(http.MethodPut, http.MethodOptions)
+ r0mux.Handle("/publicRooms",
+ httputil.MakeExternalAPI("public_rooms", func(req *http.Request) util.JSONResponse {
+ /* TODO:
+ if extRoomsProvider != nil {
+ return GetPostPublicRoomsWithExternal(req, stateAPI, fedClient, extRoomsProvider)
+ } */
+ return GetPostPublicRooms(req, rsAPI, stateAPI)
+ }),
+ ).Methods(http.MethodGet, http.MethodPost, http.MethodOptions)
r0mux.Handle("/logout",
httputil.MakeAuthAPI("logout", userAPI, func(req *http.Request, device *api.Device) util.JSONResponse {