diff --git a/clientapi/jsonerror/jsonerror.go b/clientapi/jsonerror/jsonerror.go index be7d13a96..4dc3d8603 100644 --- a/clientapi/jsonerror/jsonerror.go +++ b/clientapi/jsonerror/jsonerror.go @@ -171,6 +171,15 @@ func LeaveServerNoticeError() *MatrixError { } } +// ServerDisabledNoticeError is an error returned when trying to sync a server +// that is disabled +func ServerDisabledNoticeError() *MatrixError { + return &MatrixError{ + ErrCode: "Z_UNAUTHORISED_SERVER_DISABLED", + Err: "You cannot remain in a disabled server", + } +} + type IncompatibleRoomVersionError struct { RoomVersion string `json:"room_version"` Error string `json:"error"` diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index 4746f73c8..efc4210f5 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -263,13 +263,13 @@ func Setup( return util.ErrorResponse(err) } - isAllowed, _ := authorization.IsAllowed(authz.AuthorizationArgs{ + isAllowed, err := authorization.IsAllowed(authz.AuthorizationArgs{ RoomId: vars["roomIDOrAlias"], UserId: device.UserID, Permission: authz.PermissionRead, }) - if !isAllowed { + if !isAllowed || err != nil { return util.JSONResponse{ Code: http.StatusUnauthorized, JSON: jsonerror.Forbidden("Unauthorised"), diff --git a/cmd/dendrite-polylith-multi/personalities/syncapi.go b/cmd/dendrite-polylith-multi/personalities/syncapi.go index 41637fe1d..1f9cb86e0 100644 --- a/cmd/dendrite-polylith-multi/personalities/syncapi.go +++ b/cmd/dendrite-polylith-multi/personalities/syncapi.go @@ -22,12 +22,13 @@ import ( func SyncAPI(base *basepkg.BaseDendrite, cfg *config.Dendrite) { userAPI := base.UserAPIClient() + base.RoomserverHTTPClient() rsAPI := base.RoomserverHTTPClient() syncapi.AddPublicRoutes( base, - userAPI, rsAPI, + userAPI, rsAPI, rsAPI, base.KeyServerHTTPClient(), ) diff --git a/setup/monolith.go b/setup/monolith.go index 41a897024..18bfa2188 100644 --- a/setup/monolith.go +++ b/setup/monolith.go @@ -69,6 +69,6 @@ func (m *Monolith) AddAllPublicRoutes(base *base.BaseDendrite) { base, m.UserAPI, m.Client, ) syncapi.AddPublicRoutes( - base, m.UserAPI, m.RoomserverAPI, m.KeyAPI, + base, m.UserAPI, m.RoomserverAPI, m.RoomserverAPI, m.KeyAPI, ) } diff --git a/syncapi/routing/routing.go b/syncapi/routing/routing.go index 558606f93..666ee0b29 100644 --- a/syncapi/routing/routing.go +++ b/syncapi/routing/routing.go @@ -22,6 +22,7 @@ import ( "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" @@ -45,6 +46,7 @@ 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, @@ -63,21 +65,44 @@ func Setup( 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)) - isAllowed, _ := authorization.IsAllowed(authz.AuthorizationArgs{ + 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"), } } - if err != nil { - return util.ErrorResponse(err) - } + return OnIncomingMessagesRequest(req, syncDB, vars["roomID"], device, rsAPI, cfg, srp, lazyLoadCache) })).Methods(http.MethodGet, http.MethodOptions) diff --git a/syncapi/syncapi.go b/syncapi/syncapi.go index 4b07366c5..fb484dcfd 100644 --- a/syncapi/syncapi.go +++ b/syncapi/syncapi.go @@ -42,6 +42,7 @@ func AddPublicRoutes( base *base.BaseDendrite, userAPI userapi.SyncUserAPI, rsAPI api.SyncRoomserverAPI, + crsAPI api.ClientRoomserverAPI, keyAPI keyapi.SyncKeyAPI, ) { cfg := &base.Cfg.SyncAPI @@ -133,6 +134,6 @@ func AddPublicRoutes( routing.Setup( base.PublicClientAPIMux, requestPool, syncDB, userAPI, - rsAPI, cfg, clientCfg, base.Caches, base.Fulltext, + rsAPI, crsAPI, cfg, clientCfg, base.Caches, base.Fulltext, ) } diff --git a/syncapi/syncapi_test.go b/syncapi/syncapi_test.go index a4985dbf4..ba01a46e4 100644 --- a/syncapi/syncapi_test.go +++ b/syncapi/syncapi_test.go @@ -32,6 +32,10 @@ type syncRoomserverAPI struct { rooms []*test.Room } +type clientRoomserverAPI struct { + rsapi.ClientRoomserverAPI +} + func (s *syncRoomserverAPI) QueryLatestEventsAndState(ctx context.Context, req *rsapi.QueryLatestEventsAndStateRequest, res *rsapi.QueryLatestEventsAndStateResponse) error { var room *test.Room for _, r := range s.rooms { @@ -120,7 +124,7 @@ func testSyncAccessTokens(t *testing.T, dbType test.DBType) { jsctx, _ := base.NATS.Prepare(base.ProcessContext, &base.Cfg.Global.JetStream) defer jetstream.DeleteAllStreams(jsctx, &base.Cfg.Global.JetStream) msgs := toNATSMsgs(t, base, room.Events()...) - AddPublicRoutes(base, &syncUserAPI{accounts: []userapi.Device{alice}}, &syncRoomserverAPI{rooms: []*test.Room{room}}, &syncKeyAPI{}) + AddPublicRoutes(base, &syncUserAPI{accounts: []userapi.Device{alice}}, &syncRoomserverAPI{rooms: []*test.Room{room}}, &clientRoomserverAPI{}, &syncKeyAPI{}) testrig.MustPublishMsgs(t, jsctx, msgs...) testCases := []struct { @@ -219,7 +223,7 @@ func testSyncAPICreateRoomSyncEarly(t *testing.T, dbType test.DBType) { // m.room.history_visibility msgs := toNATSMsgs(t, base, room.Events()...) sinceTokens := make([]string, len(msgs)) - AddPublicRoutes(base, &syncUserAPI{accounts: []userapi.Device{alice}}, &syncRoomserverAPI{rooms: []*test.Room{room}}, &syncKeyAPI{}) + AddPublicRoutes(base, &syncUserAPI{accounts: []userapi.Device{alice}}, &syncRoomserverAPI{rooms: []*test.Room{room}}, &clientRoomserverAPI{}, &syncKeyAPI{}) for i, msg := range msgs { testrig.MustPublishMsgs(t, jsctx, msg) time.Sleep(100 * time.Millisecond) @@ -303,7 +307,7 @@ func testSyncAPIUpdatePresenceImmediately(t *testing.T, dbType test.DBType) { jsctx, _ := base.NATS.Prepare(base.ProcessContext, &base.Cfg.Global.JetStream) defer jetstream.DeleteAllStreams(jsctx, &base.Cfg.Global.JetStream) - AddPublicRoutes(base, &syncUserAPI{accounts: []userapi.Device{alice}}, &syncRoomserverAPI{}, &syncKeyAPI{}) + AddPublicRoutes(base, &syncUserAPI{accounts: []userapi.Device{alice}}, &syncRoomserverAPI{}, &clientRoomserverAPI{}, &syncKeyAPI{}) w := httptest.NewRecorder() base.PublicClientAPIMux.ServeHTTP(w, test.NewRequest(t, "GET", "/_matrix/client/v3/sync", test.WithQueryParams(map[string]string{ "access_token": alice.AccessToken, @@ -421,7 +425,7 @@ func testHistoryVisibility(t *testing.T, dbType test.DBType) { rsAPI := roomserver.NewInternalAPI(base) rsAPI.SetFederationAPI(nil, nil) - AddPublicRoutes(base, &syncUserAPI{accounts: []userapi.Device{aliceDev, bobDev}}, rsAPI, &syncKeyAPI{}) + AddPublicRoutes(base, &syncUserAPI{accounts: []userapi.Device{aliceDev, bobDev}}, rsAPI, rsAPI, &syncKeyAPI{}) for _, tc := range testCases { testname := fmt.Sprintf("%s - %s", tc.historyVisibility, userType) @@ -541,7 +545,7 @@ func testSendToDevice(t *testing.T, dbType test.DBType) { jsctx, _ := base.NATS.Prepare(base.ProcessContext, &base.Cfg.Global.JetStream) defer jetstream.DeleteAllStreams(jsctx, &base.Cfg.Global.JetStream) - AddPublicRoutes(base, &syncUserAPI{accounts: []userapi.Device{alice}}, &syncRoomserverAPI{}, &syncKeyAPI{}) + AddPublicRoutes(base, &syncUserAPI{accounts: []userapi.Device{alice}}, &syncRoomserverAPI{}, &clientRoomserverAPI{}, &syncKeyAPI{}) producer := producers.SyncAPIProducer{ TopicSendToDeviceEvent: base.Cfg.Global.JetStream.Prefixed(jetstream.OutputSendToDeviceEvent), diff --git a/zion/zion_authorization.go b/zion/zion_authorization.go index 155116dd7..01219ebcc 100644 --- a/zion/zion_authorization.go +++ b/zion/zion_authorization.go @@ -2,6 +2,7 @@ package zion import ( _ "embed" + "errors" "github.com/ethereum/go-ethereum/common" "github.com/matrix-org/dendrite/authorization" @@ -18,6 +19,9 @@ var localhostJson []byte //go:embed contracts/zion_goerli/space-manager.json var goerliJson []byte +var ErrSpaceDisabled = errors.New("space disabled") +var ErrChannelDisabled = errors.New("channel disabled") + type ZionAuthorization struct { store StoreAPI spaceManagerLocalhost *zion_localhost.ZionSpaceManagerLocalhost @@ -95,8 +99,22 @@ func (za *ZionAuthorization) IsAllowed(args authorization.AuthorizationArgs) (bo switch za.chainId { case 1337, 31337: + // Check if space / channel is disabled. + disabled, err := za.isSpaceChannelDisabledLocalhost(roomInfo) + if disabled { + return false, ErrSpaceDisabled + } else if err != nil { + return false, err + } return za.isAllowedLocalhost(roomInfo, userIdentifier.AccountAddress, args.Permission) case 5: + // Check if space / channel is disabled. + disabled, err := za.isSpaceChannelDisabledGoerli(roomInfo) + if disabled { + return false, ErrChannelDisabled + } else if err != nil { + return false, err + } return za.isAllowedGoerli(roomInfo, userIdentifier.AccountAddress, args.Permission) default: log.Errorf("Unsupported chain id: %d", userIdentifier.ChainId) @@ -105,6 +123,36 @@ func (za *ZionAuthorization) IsAllowed(args authorization.AuthorizationArgs) (bo return false, nil } +func (za *ZionAuthorization) isSpaceChannelDisabledLocalhost(roomInfo RoomInfo) (bool, error) { + if za.spaceManagerLocalhost == nil { + return false, errors.New("error fetching space manager contract") + } + switch roomInfo.ChannelNetworkId { + case "": + spInfo, err := za.spaceManagerLocalhost.GetSpaceInfoBySpaceId(nil, roomInfo.SpaceNetworkId) + return spInfo.Disabled, err + default: + chInfo, err := za.spaceManagerLocalhost.GetChannelInfoByChannelId(nil, roomInfo.SpaceNetworkId, roomInfo.ChannelNetworkId) + return chInfo.Disabled, err + } + +} + +func (za *ZionAuthorization) isSpaceChannelDisabledGoerli(roomInfo RoomInfo) (bool, error) { + if za.spaceManagerGoerli == nil { + return false, errors.New("error fetching space manager contract") + } + switch roomInfo.ChannelNetworkId { + case "": + spInfo, err := za.spaceManagerGoerli.GetSpaceInfoBySpaceId(nil, roomInfo.SpaceNetworkId) + return spInfo.Disabled, err + default: + chInfo, err := za.spaceManagerGoerli.GetChannelInfoByChannelId(nil, roomInfo.SpaceNetworkId, roomInfo.ChannelNetworkId) + return chInfo.Disabled, err + } + +} + func (za *ZionAuthorization) isAllowedLocalhost( roomInfo RoomInfo, user common.Address,