mirror of
https://github.com/hoernschen/dendrite.git
synced 2025-07-30 04:52:46 +00:00
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
This commit is contained in:
parent
55bc82c439
commit
4c1e6597c0
41 changed files with 1481 additions and 79 deletions
|
@ -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,
|
||||
|
|
|
@ -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{}{},
|
||||
}
|
||||
}
|
||||
|
|
373
clientapi/routing/directory_public.go
Normal file
373
clientapi/routing/directory_public.go
Normal file
|
@ -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, ¤tstateAPI.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
|
||||
}
|
48
clientapi/routing/directory_public_test.go
Normal file
48
clientapi/routing/directory_public_test.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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, ¤tstateAPI.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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue