aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--clientapi/routing/admin.go37
-rw-r--r--clientapi/routing/routing.go6
-rw-r--r--docs/administration/4_adminapi.md6
-rw-r--r--roomserver/api/api.go8
-rw-r--r--roomserver/api/api_trace.go9
-rw-r--r--roomserver/api/perform.go9
-rw-r--r--roomserver/internal/api.go1
-rw-r--r--roomserver/internal/perform/perform_admin.go70
-rw-r--r--roomserver/inthttp/client.go18
-rw-r--r--roomserver/inthttp/server.go11
-rw-r--r--userapi/internal/api.go26
-rw-r--r--userapi/userapi.go1
12 files changed, 197 insertions, 5 deletions
diff --git a/clientapi/routing/admin.go b/clientapi/routing/admin.go
index 125b3847..523b88c9 100644
--- a/clientapi/routing/admin.go
+++ b/clientapi/routing/admin.go
@@ -47,3 +47,40 @@ func AdminEvacuateRoom(req *http.Request, device *userapi.Device, rsAPI roomserv
},
}
}
+
+func AdminEvacuateUser(req *http.Request, device *userapi.Device, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse {
+ if device.AccountType != userapi.AccountTypeAdmin {
+ return util.JSONResponse{
+ Code: http.StatusForbidden,
+ JSON: jsonerror.Forbidden("This API can only be used by admin users."),
+ }
+ }
+ vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
+ if err != nil {
+ return util.ErrorResponse(err)
+ }
+ userID, ok := vars["userID"]
+ if !ok {
+ return util.JSONResponse{
+ Code: http.StatusBadRequest,
+ JSON: jsonerror.MissingArgument("Expecting user ID."),
+ }
+ }
+ res := &roomserverAPI.PerformAdminEvacuateUserResponse{}
+ rsAPI.PerformAdminEvacuateUser(
+ req.Context(),
+ &roomserverAPI.PerformAdminEvacuateUserRequest{
+ UserID: userID,
+ },
+ res,
+ )
+ if err := res.Error; err != nil {
+ return err.JSONResponse()
+ }
+ return util.JSONResponse{
+ Code: 200,
+ JSON: map[string]interface{}{
+ "affected": res.Affected,
+ },
+ }
+}
diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go
index aa4b5a23..0460850e 100644
--- a/clientapi/routing/routing.go
+++ b/clientapi/routing/routing.go
@@ -129,6 +129,12 @@ func Setup(
}),
).Methods(http.MethodGet, http.MethodOptions)
+ dendriteAdminRouter.Handle("/admin/evacuateUser/{userID}",
+ httputil.MakeAuthAPI("admin_evacuate_user", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
+ return AdminEvacuateUser(req, device, rsAPI)
+ }),
+ ).Methods(http.MethodGet, http.MethodOptions)
+
// server notifications
if cfg.Matrix.ServerNotices.Enabled {
logrus.Info("Enabling server notices at /_synapse/admin/v1/send_server_notice")
diff --git a/docs/administration/4_adminapi.md b/docs/administration/4_adminapi.md
index e33482ec..51f56374 100644
--- a/docs/administration/4_adminapi.md
+++ b/docs/administration/4_adminapi.md
@@ -19,6 +19,12 @@ This endpoint will instruct Dendrite to part all local users from the given `roo
in the URL. It may take some time to complete. A JSON body will be returned containing
the user IDs of all affected users.
+## `/_dendrite/admin/evacuateUser/{userID}`
+
+This endpoint will instruct Dendrite to part the given local `userID` in the URL from
+all rooms which they are currently joined. A JSON body will be returned containing
+the room IDs of all affected rooms.
+
## `/_synapse/admin/v1/register`
Shared secret registration — please see the [user creation page](createusers) for
diff --git a/roomserver/api/api.go b/roomserver/api/api.go
index f87ff296..38baa617 100644
--- a/roomserver/api/api.go
+++ b/roomserver/api/api.go
@@ -140,11 +140,8 @@ type ClientRoomserverAPI interface {
// PerformRoomUpgrade upgrades a room to a newer version
PerformRoomUpgrade(ctx context.Context, req *PerformRoomUpgradeRequest, resp *PerformRoomUpgradeResponse)
- PerformAdminEvacuateRoom(
- ctx context.Context,
- req *PerformAdminEvacuateRoomRequest,
- res *PerformAdminEvacuateRoomResponse,
- )
+ PerformAdminEvacuateRoom(ctx context.Context, req *PerformAdminEvacuateRoomRequest, res *PerformAdminEvacuateRoomResponse)
+ PerformAdminEvacuateUser(ctx context.Context, req *PerformAdminEvacuateUserRequest, res *PerformAdminEvacuateUserResponse)
PerformPeek(ctx context.Context, req *PerformPeekRequest, res *PerformPeekResponse)
PerformUnpeek(ctx context.Context, req *PerformUnpeekRequest, res *PerformUnpeekResponse)
PerformInvite(ctx context.Context, req *PerformInviteRequest, res *PerformInviteResponse) error
@@ -161,6 +158,7 @@ type UserRoomserverAPI interface {
QueryLatestEventsAndStateAPI
QueryCurrentState(ctx context.Context, req *QueryCurrentStateRequest, res *QueryCurrentStateResponse) error
QueryMembershipsForRoom(ctx context.Context, req *QueryMembershipsForRoomRequest, res *QueryMembershipsForRoomResponse) error
+ PerformAdminEvacuateUser(ctx context.Context, req *PerformAdminEvacuateUserRequest, res *PerformAdminEvacuateUserResponse)
}
type FederationRoomserverAPI interface {
diff --git a/roomserver/api/api_trace.go b/roomserver/api/api_trace.go
index 92c5c1b1..211f320f 100644
--- a/roomserver/api/api_trace.go
+++ b/roomserver/api/api_trace.go
@@ -113,6 +113,15 @@ func (t *RoomserverInternalAPITrace) PerformAdminEvacuateRoom(
util.GetLogger(ctx).Infof("PerformAdminEvacuateRoom req=%+v res=%+v", js(req), js(res))
}
+func (t *RoomserverInternalAPITrace) PerformAdminEvacuateUser(
+ ctx context.Context,
+ req *PerformAdminEvacuateUserRequest,
+ res *PerformAdminEvacuateUserResponse,
+) {
+ t.Impl.PerformAdminEvacuateUser(ctx, req, res)
+ util.GetLogger(ctx).Infof("PerformAdminEvacuateUser req=%+v res=%+v", js(req), js(res))
+}
+
func (t *RoomserverInternalAPITrace) PerformInboundPeek(
ctx context.Context,
req *PerformInboundPeekRequest,
diff --git a/roomserver/api/perform.go b/roomserver/api/perform.go
index 30aa2cf1..d9ea9dd1 100644
--- a/roomserver/api/perform.go
+++ b/roomserver/api/perform.go
@@ -223,3 +223,12 @@ type PerformAdminEvacuateRoomResponse struct {
Affected []string `json:"affected"`
Error *PerformError
}
+
+type PerformAdminEvacuateUserRequest struct {
+ UserID string `json:"user_id"`
+}
+
+type PerformAdminEvacuateUserResponse struct {
+ Affected []string `json:"affected"`
+ Error *PerformError
+}
diff --git a/roomserver/internal/api.go b/roomserver/internal/api.go
index afef52da..acdaeef6 100644
--- a/roomserver/internal/api.go
+++ b/roomserver/internal/api.go
@@ -170,6 +170,7 @@ func (r *RoomserverInternalAPI) SetFederationAPI(fsAPI fsAPI.RoomserverFederatio
Cfg: r.Cfg,
Inputer: r.Inputer,
Queryer: r.Queryer,
+ Leaver: r.Leaver,
}
if err := r.Inputer.Start(); err != nil {
diff --git a/roomserver/internal/perform/perform_admin.go b/roomserver/internal/perform/perform_admin.go
index 2de6477c..d3fb7109 100644
--- a/roomserver/internal/perform/perform_admin.go
+++ b/roomserver/internal/perform/perform_admin.go
@@ -16,6 +16,7 @@ package perform
import (
"context"
+ "database/sql"
"encoding/json"
"fmt"
"time"
@@ -34,6 +35,7 @@ type Admin struct {
Cfg *config.RoomServer
Queryer *query.Queryer
Inputer *input.Inputer
+ Leaver *Leaver
}
// PerformEvacuateRoom will remove all local users from the given room.
@@ -160,3 +162,71 @@ func (r *Admin) PerformAdminEvacuateRoom(
inputRes := &api.InputRoomEventsResponse{}
r.Inputer.InputRoomEvents(ctx, inputReq, inputRes)
}
+
+func (r *Admin) PerformAdminEvacuateUser(
+ ctx context.Context,
+ req *api.PerformAdminEvacuateUserRequest,
+ res *api.PerformAdminEvacuateUserResponse,
+) {
+ _, domain, err := gomatrixserverlib.SplitID('@', req.UserID)
+ if err != nil {
+ res.Error = &api.PerformError{
+ Code: api.PerformErrorBadRequest,
+ Msg: fmt.Sprintf("Malformed user ID: %s", err),
+ }
+ return
+ }
+ if domain != r.Cfg.Matrix.ServerName {
+ res.Error = &api.PerformError{
+ Code: api.PerformErrorBadRequest,
+ Msg: "Can only evacuate local users using this endpoint",
+ }
+ return
+ }
+
+ roomIDs, err := r.DB.GetRoomsByMembership(ctx, req.UserID, gomatrixserverlib.Join)
+ if err != nil && err != sql.ErrNoRows {
+ res.Error = &api.PerformError{
+ Code: api.PerformErrorBadRequest,
+ Msg: fmt.Sprintf("r.DB.GetRoomsByMembership: %s", err),
+ }
+ return
+ }
+
+ inviteRoomIDs, err := r.DB.GetRoomsByMembership(ctx, req.UserID, gomatrixserverlib.Invite)
+ if err != nil && err != sql.ErrNoRows {
+ res.Error = &api.PerformError{
+ Code: api.PerformErrorBadRequest,
+ Msg: fmt.Sprintf("r.DB.GetRoomsByMembership: %s", err),
+ }
+ return
+ }
+
+ for _, roomID := range append(roomIDs, inviteRoomIDs...) {
+ leaveReq := &api.PerformLeaveRequest{
+ RoomID: roomID,
+ UserID: req.UserID,
+ }
+ leaveRes := &api.PerformLeaveResponse{}
+ outputEvents, err := r.Leaver.PerformLeave(ctx, leaveReq, leaveRes)
+ if err != nil {
+ res.Error = &api.PerformError{
+ Code: api.PerformErrorBadRequest,
+ Msg: fmt.Sprintf("r.Leaver.PerformLeave: %s", err),
+ }
+ return
+ }
+ if len(outputEvents) == 0 {
+ continue
+ }
+ if err := r.Inputer.WriteOutputEvents(roomID, outputEvents); err != nil {
+ res.Error = &api.PerformError{
+ Code: api.PerformErrorBadRequest,
+ Msg: fmt.Sprintf("r.Inputer.WriteOutputEvents: %s", err),
+ }
+ return
+ }
+
+ res.Affected = append(res.Affected, roomID)
+ }
+}
diff --git a/roomserver/inthttp/client.go b/roomserver/inthttp/client.go
index 7b10ae65..2fa8afc4 100644
--- a/roomserver/inthttp/client.go
+++ b/roomserver/inthttp/client.go
@@ -40,6 +40,7 @@ const (
RoomserverPerformInboundPeekPath = "/roomserver/performInboundPeek"
RoomserverPerformForgetPath = "/roomserver/performForget"
RoomserverPerformAdminEvacuateRoomPath = "/roomserver/performAdminEvacuateRoom"
+ RoomserverPerformAdminEvacuateUserPath = "/roomserver/performAdminEvacuateUser"
// Query operations
RoomserverQueryLatestEventsAndStatePath = "/roomserver/queryLatestEventsAndState"
@@ -305,6 +306,23 @@ func (h *httpRoomserverInternalAPI) PerformAdminEvacuateRoom(
}
}
+func (h *httpRoomserverInternalAPI) PerformAdminEvacuateUser(
+ ctx context.Context,
+ req *api.PerformAdminEvacuateUserRequest,
+ res *api.PerformAdminEvacuateUserResponse,
+) {
+ span, ctx := opentracing.StartSpanFromContext(ctx, "PerformAdminEvacuateUser")
+ defer span.Finish()
+
+ apiURL := h.roomserverURL + RoomserverPerformAdminEvacuateUserPath
+ err := httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res)
+ if err != nil {
+ res.Error = &api.PerformError{
+ Msg: fmt.Sprintf("failed to communicate with roomserver: %s", err),
+ }
+ }
+}
+
// QueryLatestEventsAndState implements RoomserverQueryAPI
func (h *httpRoomserverInternalAPI) QueryLatestEventsAndState(
ctx context.Context,
diff --git a/roomserver/inthttp/server.go b/roomserver/inthttp/server.go
index ad4fdc46..99338158 100644
--- a/roomserver/inthttp/server.go
+++ b/roomserver/inthttp/server.go
@@ -129,6 +129,17 @@ func AddRoutes(r api.RoomserverInternalAPI, internalAPIMux *mux.Router) {
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
}),
)
+ internalAPIMux.Handle(RoomserverPerformAdminEvacuateUserPath,
+ httputil.MakeInternalAPI("performAdminEvacuateUser", func(req *http.Request) util.JSONResponse {
+ var request api.PerformAdminEvacuateUserRequest
+ var response api.PerformAdminEvacuateUserResponse
+ if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
+ return util.MessageResponse(http.StatusBadRequest, err.Error())
+ }
+ r.PerformAdminEvacuateUser(req.Context(), &request, &response)
+ return util.JSONResponse{Code: http.StatusOK, JSON: &response}
+ }),
+ )
internalAPIMux.Handle(
RoomserverQueryPublishedRoomsPath,
httputil.MakeInternalAPI("queryPublishedRooms", func(req *http.Request) util.JSONResponse {
diff --git a/userapi/internal/api.go b/userapi/internal/api.go
index 9d2f63c7..27ed15a0 100644
--- a/userapi/internal/api.go
+++ b/userapi/internal/api.go
@@ -33,6 +33,7 @@ import (
"github.com/matrix-org/dendrite/internal/pushrules"
"github.com/matrix-org/dendrite/internal/sqlutil"
keyapi "github.com/matrix-org/dendrite/keyserver/api"
+ rsapi "github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/dendrite/userapi/producers"
@@ -49,6 +50,7 @@ type UserInternalAPI struct {
// AppServices is the list of all registered AS
AppServices []config.ApplicationService
KeyAPI keyapi.UserKeyAPI
+ RSAPI rsapi.UserRoomserverAPI
}
func (a *UserInternalAPI) InputAccountData(ctx context.Context, req *api.InputAccountDataRequest, res *api.InputAccountDataResponse) error {
@@ -452,6 +454,30 @@ func (a *UserInternalAPI) queryAppServiceToken(ctx context.Context, token, appSe
// PerformAccountDeactivation deactivates the user's account, removing all ability for the user to login again.
func (a *UserInternalAPI) PerformAccountDeactivation(ctx context.Context, req *api.PerformAccountDeactivationRequest, res *api.PerformAccountDeactivationResponse) error {
+ evacuateReq := &rsapi.PerformAdminEvacuateUserRequest{
+ UserID: fmt.Sprintf("@%s:%s", req.Localpart, a.ServerName),
+ }
+ evacuateRes := &rsapi.PerformAdminEvacuateUserResponse{}
+ a.RSAPI.PerformAdminEvacuateUser(ctx, evacuateReq, evacuateRes)
+ if err := evacuateRes.Error; err != nil {
+ logrus.WithError(err).Errorf("Failed to evacuate user after account deactivation")
+ }
+
+ deviceReq := &api.PerformDeviceDeletionRequest{
+ UserID: fmt.Sprintf("@%s:%s", req.Localpart, a.ServerName),
+ }
+ deviceRes := &api.PerformDeviceDeletionResponse{}
+ if err := a.PerformDeviceDeletion(ctx, deviceReq, deviceRes); err != nil {
+ return err
+ }
+
+ pusherReq := &api.PerformPusherDeletionRequest{
+ Localpart: req.Localpart,
+ }
+ if err := a.PerformPusherDeletion(ctx, pusherReq, &struct{}{}); err != nil {
+ return err
+ }
+
err := a.DB.DeactivateAccount(ctx, req.Localpart)
res.AccountDeactivated = err == nil
return err
diff --git a/userapi/userapi.go b/userapi/userapi.go
index 603b416b..2e86d6aa 100644
--- a/userapi/userapi.go
+++ b/userapi/userapi.go
@@ -78,6 +78,7 @@ func NewInternalAPI(
ServerName: cfg.Matrix.ServerName,
AppServices: appServices,
KeyAPI: keyAPI,
+ RSAPI: rsAPI,
DisableTLSValidation: cfg.PushGatewayDisableTLSValidation,
}