mirror of
https://github.com/matrix-org/dendrite.git
synced 2026-01-17 02:53:11 -06:00
Closes HNT-244. The following PR implements Space,Channel soft deletion using on-chain `disabled` flag scope to space, channel respectively. On message sync, dendrite will now gate disabled rooms by performing a leave on the user attempting to sync unless the user is the owner (more on this later). To re-join, given rooms (spaces,channels) are created by default using `invite` membership state, the owner will need to undo the on-chain `disabled` flag, setting it false then re-invite users that left the room as a side effect of it becoming disabled previously. The owner does not leave the space, channel because if they did then there would be no one left to invite users let alone themselves back in if the action is ever undone. What is not implemented in this PR: 1. **Transitive leaves on channels in a space** - If a space is disabled, users will leave the space but not the channels within the space. To allow for fully disabling a space and all its' channels, the client can offer a view to the owner that iterates over the channels and space to disable all on-chain. Furthermore, we could implement a batch on-chain method that fully disables all channels within a space (plus the space) in one on-chain call to save the owner gas. 2. **Data deletion** - No data is remove from the DAGs or on-chain. Therefore deletion is soft and reversible. 3. **New hook to check if a room is disabled** - the client can leverage existing on-chain public read only methods `getSpaceInfoBySpaceId`, `getChannelInfoByChannelId` to read the state of each in order to remove spaces, channels from a member's view that are disabled.
254 lines
8.8 KiB
Go
254 lines
8.8 KiB
Go
// Copyright 2017 Vector Creations Ltd
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package routing
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/gorilla/mux"
|
|
"github.com/matrix-org/gomatrixserverlib"
|
|
"github.com/matrix-org/util"
|
|
|
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
"github.com/matrix-org/dendrite/clientapi/routing"
|
|
"github.com/matrix-org/dendrite/internal/caching"
|
|
"github.com/matrix-org/dendrite/internal/fulltext"
|
|
"github.com/matrix-org/dendrite/internal/httputil"
|
|
"github.com/matrix-org/dendrite/roomserver/api"
|
|
"github.com/matrix-org/dendrite/setup/config"
|
|
"github.com/matrix-org/dendrite/syncapi/storage"
|
|
"github.com/matrix-org/dendrite/syncapi/sync"
|
|
|
|
authz "github.com/matrix-org/dendrite/authorization"
|
|
clientApiAuthz "github.com/matrix-org/dendrite/clientapi/authorization"
|
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
|
zion "github.com/matrix-org/dendrite/zion"
|
|
)
|
|
|
|
// Setup configures the given mux with sync-server listeners
|
|
//
|
|
// Due to Setup being used to call many other functions, a gocyclo nolint is
|
|
// applied:
|
|
// nolint: gocyclo
|
|
func Setup(
|
|
csMux *mux.Router, srp *sync.RequestPool, syncDB storage.Database,
|
|
userAPI userapi.SyncUserAPI,
|
|
rsAPI api.SyncRoomserverAPI,
|
|
crsAPI api.ClientRoomserverAPI,
|
|
cfg *config.SyncAPI,
|
|
clientCfg *config.ClientAPI,
|
|
lazyLoadCache caching.LazyLoadCache,
|
|
fts *fulltext.Search,
|
|
) {
|
|
syncAuthz := zion.SyncRoomserverStruct{SyncRoomserverAPI: rsAPI}
|
|
authorization := clientApiAuthz.NewRoomserverAuthorization(clientCfg, syncAuthz)
|
|
v1unstablemux := csMux.PathPrefix("/{apiversion:(?:v1|unstable)}/").Subrouter()
|
|
v3mux := csMux.PathPrefix("/{apiversion:(?:r0|v3)}/").Subrouter()
|
|
|
|
// TODO: Add AS support for all handlers below.
|
|
v3mux.Handle("/sync", httputil.MakeAuthAPI("sync", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
|
return srp.OnIncomingSyncRequest(req, device)
|
|
})).Methods(http.MethodGet, http.MethodOptions)
|
|
|
|
v3mux.Handle("/rooms/{roomID}/messages", httputil.MakeAuthAPI("room_messages", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
|
|
|
if err != nil {
|
|
return util.ErrorResponse(err)
|
|
}
|
|
|
|
isAllowed, err := authorization.IsAllowed(authz.AuthorizationArgs{
|
|
RoomId: vars["roomID"],
|
|
UserId: device.UserID,
|
|
Permission: authz.PermissionRead,
|
|
})
|
|
|
|
if err != nil {
|
|
switch err {
|
|
case zion.ErrSpaceDisabled, zion.ErrChannelDisabled:
|
|
// leave space / channel if it is disabled
|
|
resp := routing.LeaveRoomByID(req, device, crsAPI, vars["roomID"])
|
|
if resp.Code == 200 {
|
|
return util.JSONResponse{
|
|
Code: http.StatusUnauthorized,
|
|
JSON: jsonerror.ServerDisabledNoticeError(),
|
|
}
|
|
}
|
|
return resp
|
|
default:
|
|
// error client if something else is awry
|
|
return util.JSONResponse{
|
|
Code: http.StatusUnauthorized,
|
|
JSON: jsonerror.Forbidden("Unauthorised"),
|
|
}
|
|
}
|
|
}
|
|
|
|
if !isAllowed {
|
|
return util.JSONResponse{
|
|
Code: http.StatusUnauthorized,
|
|
JSON: jsonerror.Forbidden("Unauthorised"),
|
|
}
|
|
}
|
|
|
|
return OnIncomingMessagesRequest(req, syncDB, vars["roomID"], device, rsAPI, cfg, srp, lazyLoadCache)
|
|
})).Methods(http.MethodGet, http.MethodOptions)
|
|
|
|
v3mux.Handle("/rooms/{roomID}/event/{eventID}",
|
|
httputil.MakeAuthAPI("rooms_get_event", 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 GetEvent(req, device, vars["roomID"], vars["eventID"], cfg, syncDB, rsAPI)
|
|
}),
|
|
).Methods(http.MethodGet, http.MethodOptions)
|
|
|
|
v3mux.Handle("/user/{userId}/filter",
|
|
httputil.MakeAuthAPI("put_filter", 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 PutFilter(req, device, syncDB, vars["userId"])
|
|
}),
|
|
).Methods(http.MethodPost, http.MethodOptions)
|
|
|
|
v3mux.Handle("/user/{userId}/filter/{filterId}",
|
|
httputil.MakeAuthAPI("get_filter", 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 GetFilter(req, device, syncDB, vars["userId"], vars["filterId"])
|
|
}),
|
|
).Methods(http.MethodGet, http.MethodOptions)
|
|
|
|
v3mux.Handle("/keys/changes", httputil.MakeAuthAPI("keys_changes", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
|
return srp.OnIncomingKeyChangeRequest(req, device)
|
|
})).Methods(http.MethodGet, http.MethodOptions)
|
|
|
|
v3mux.Handle("/rooms/{roomId}/context/{eventId}",
|
|
httputil.MakeAuthAPI(gomatrixserverlib.Join, 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 Context(
|
|
req, device,
|
|
rsAPI, syncDB,
|
|
vars["roomId"], vars["eventId"],
|
|
lazyLoadCache,
|
|
)
|
|
}),
|
|
).Methods(http.MethodGet, http.MethodOptions)
|
|
|
|
v1unstablemux.Handle("/rooms/{roomId}/relations/{eventId}",
|
|
httputil.MakeAuthAPI(gomatrixserverlib.Join, 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 Relations(
|
|
req, device, syncDB, rsAPI,
|
|
vars["roomId"], vars["eventId"], "", "",
|
|
)
|
|
}),
|
|
).Methods(http.MethodGet, http.MethodOptions)
|
|
|
|
v1unstablemux.Handle("/rooms/{roomId}/relations/{eventId}/{relType}",
|
|
httputil.MakeAuthAPI(gomatrixserverlib.Join, 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 Relations(
|
|
req, device, syncDB, rsAPI,
|
|
vars["roomId"], vars["eventId"], vars["relType"], "",
|
|
)
|
|
}),
|
|
).Methods(http.MethodGet, http.MethodOptions)
|
|
|
|
v1unstablemux.Handle("/rooms/{roomId}/relations/{eventId}/{relType}/{eventType}",
|
|
httputil.MakeAuthAPI(gomatrixserverlib.Join, 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 Relations(
|
|
req, device, syncDB, rsAPI,
|
|
vars["roomId"], vars["eventId"], vars["relType"], vars["eventType"],
|
|
)
|
|
}),
|
|
).Methods(http.MethodGet, http.MethodOptions)
|
|
|
|
v3mux.Handle("/search",
|
|
httputil.MakeAuthAPI("search", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
|
if !cfg.Fulltext.Enabled {
|
|
return util.JSONResponse{
|
|
Code: http.StatusNotImplemented,
|
|
JSON: jsonerror.Unknown("Search has been disabled by the server administrator."),
|
|
}
|
|
}
|
|
var nextBatch *string
|
|
if err := req.ParseForm(); err != nil {
|
|
return jsonerror.InternalServerError()
|
|
}
|
|
if req.Form.Has("next_batch") {
|
|
nb := req.FormValue("next_batch")
|
|
nextBatch = &nb
|
|
}
|
|
return Search(req, device, syncDB, fts, nextBatch)
|
|
}),
|
|
).Methods(http.MethodPost, http.MethodOptions)
|
|
|
|
v3mux.Handle("/rooms/{roomID}/members",
|
|
httputil.MakeAuthAPI("rooms_members", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
|
if err != nil {
|
|
return util.ErrorResponse(err)
|
|
}
|
|
var membership, notMembership *string
|
|
if req.URL.Query().Has("membership") {
|
|
m := req.URL.Query().Get("membership")
|
|
membership = &m
|
|
}
|
|
if req.URL.Query().Has("not_membership") {
|
|
m := req.URL.Query().Get("not_membership")
|
|
notMembership = &m
|
|
}
|
|
|
|
at := req.URL.Query().Get("at")
|
|
return GetMemberships(req, device, vars["roomID"], syncDB, rsAPI, false, membership, notMembership, at)
|
|
}),
|
|
).Methods(http.MethodGet, http.MethodOptions)
|
|
|
|
v3mux.Handle("/rooms/{roomID}/joined_members",
|
|
httputil.MakeAuthAPI("rooms_members", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
|
if err != nil {
|
|
return util.ErrorResponse(err)
|
|
}
|
|
at := req.URL.Query().Get("at")
|
|
membership := gomatrixserverlib.Join
|
|
return GetMemberships(req, device, vars["roomID"], syncDB, rsAPI, true, &membership, nil, at)
|
|
}),
|
|
).Methods(http.MethodGet, http.MethodOptions)
|
|
}
|