mirror of
https://github.com/hoernschen/dendrite.git
synced 2024-12-27 07:28:27 +00:00
Add evacuateUser
endpoint, use it when deactivating accounts (#2545)
* Add `evacuateUser` endpoint, use it when deactivating accounts * Populate the API * Clean up user devices when deactivating * Include invites, delete pushers
This commit is contained in:
parent
2dea466685
commit
519bc1124b
12 changed files with 197 additions and 5 deletions
|
@ -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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -129,6 +129,12 @@ func Setup(
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).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
|
// server notifications
|
||||||
if cfg.Matrix.ServerNotices.Enabled {
|
if cfg.Matrix.ServerNotices.Enabled {
|
||||||
logrus.Info("Enabling server notices at /_synapse/admin/v1/send_server_notice")
|
logrus.Info("Enabling server notices at /_synapse/admin/v1/send_server_notice")
|
||||||
|
|
|
@ -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
|
in the URL. It may take some time to complete. A JSON body will be returned containing
|
||||||
the user IDs of all affected users.
|
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`
|
## `/_synapse/admin/v1/register`
|
||||||
|
|
||||||
Shared secret registration — please see the [user creation page](createusers) for
|
Shared secret registration — please see the [user creation page](createusers) for
|
||||||
|
|
|
@ -140,11 +140,8 @@ type ClientRoomserverAPI interface {
|
||||||
|
|
||||||
// PerformRoomUpgrade upgrades a room to a newer version
|
// PerformRoomUpgrade upgrades a room to a newer version
|
||||||
PerformRoomUpgrade(ctx context.Context, req *PerformRoomUpgradeRequest, resp *PerformRoomUpgradeResponse)
|
PerformRoomUpgrade(ctx context.Context, req *PerformRoomUpgradeRequest, resp *PerformRoomUpgradeResponse)
|
||||||
PerformAdminEvacuateRoom(
|
PerformAdminEvacuateRoom(ctx context.Context, req *PerformAdminEvacuateRoomRequest, res *PerformAdminEvacuateRoomResponse)
|
||||||
ctx context.Context,
|
PerformAdminEvacuateUser(ctx context.Context, req *PerformAdminEvacuateUserRequest, res *PerformAdminEvacuateUserResponse)
|
||||||
req *PerformAdminEvacuateRoomRequest,
|
|
||||||
res *PerformAdminEvacuateRoomResponse,
|
|
||||||
)
|
|
||||||
PerformPeek(ctx context.Context, req *PerformPeekRequest, res *PerformPeekResponse)
|
PerformPeek(ctx context.Context, req *PerformPeekRequest, res *PerformPeekResponse)
|
||||||
PerformUnpeek(ctx context.Context, req *PerformUnpeekRequest, res *PerformUnpeekResponse)
|
PerformUnpeek(ctx context.Context, req *PerformUnpeekRequest, res *PerformUnpeekResponse)
|
||||||
PerformInvite(ctx context.Context, req *PerformInviteRequest, res *PerformInviteResponse) error
|
PerformInvite(ctx context.Context, req *PerformInviteRequest, res *PerformInviteResponse) error
|
||||||
|
@ -161,6 +158,7 @@ type UserRoomserverAPI interface {
|
||||||
QueryLatestEventsAndStateAPI
|
QueryLatestEventsAndStateAPI
|
||||||
QueryCurrentState(ctx context.Context, req *QueryCurrentStateRequest, res *QueryCurrentStateResponse) error
|
QueryCurrentState(ctx context.Context, req *QueryCurrentStateRequest, res *QueryCurrentStateResponse) error
|
||||||
QueryMembershipsForRoom(ctx context.Context, req *QueryMembershipsForRoomRequest, res *QueryMembershipsForRoomResponse) error
|
QueryMembershipsForRoom(ctx context.Context, req *QueryMembershipsForRoomRequest, res *QueryMembershipsForRoomResponse) error
|
||||||
|
PerformAdminEvacuateUser(ctx context.Context, req *PerformAdminEvacuateUserRequest, res *PerformAdminEvacuateUserResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
type FederationRoomserverAPI interface {
|
type FederationRoomserverAPI interface {
|
||||||
|
|
|
@ -113,6 +113,15 @@ func (t *RoomserverInternalAPITrace) PerformAdminEvacuateRoom(
|
||||||
util.GetLogger(ctx).Infof("PerformAdminEvacuateRoom req=%+v res=%+v", js(req), js(res))
|
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(
|
func (t *RoomserverInternalAPITrace) PerformInboundPeek(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *PerformInboundPeekRequest,
|
req *PerformInboundPeekRequest,
|
||||||
|
|
|
@ -223,3 +223,12 @@ type PerformAdminEvacuateRoomResponse struct {
|
||||||
Affected []string `json:"affected"`
|
Affected []string `json:"affected"`
|
||||||
Error *PerformError
|
Error *PerformError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PerformAdminEvacuateUserRequest struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PerformAdminEvacuateUserResponse struct {
|
||||||
|
Affected []string `json:"affected"`
|
||||||
|
Error *PerformError
|
||||||
|
}
|
||||||
|
|
|
@ -170,6 +170,7 @@ func (r *RoomserverInternalAPI) SetFederationAPI(fsAPI fsAPI.RoomserverFederatio
|
||||||
Cfg: r.Cfg,
|
Cfg: r.Cfg,
|
||||||
Inputer: r.Inputer,
|
Inputer: r.Inputer,
|
||||||
Queryer: r.Queryer,
|
Queryer: r.Queryer,
|
||||||
|
Leaver: r.Leaver,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.Inputer.Start(); err != nil {
|
if err := r.Inputer.Start(); err != nil {
|
||||||
|
|
|
@ -16,6 +16,7 @@ package perform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
@ -34,6 +35,7 @@ type Admin struct {
|
||||||
Cfg *config.RoomServer
|
Cfg *config.RoomServer
|
||||||
Queryer *query.Queryer
|
Queryer *query.Queryer
|
||||||
Inputer *input.Inputer
|
Inputer *input.Inputer
|
||||||
|
Leaver *Leaver
|
||||||
}
|
}
|
||||||
|
|
||||||
// PerformEvacuateRoom will remove all local users from the given room.
|
// PerformEvacuateRoom will remove all local users from the given room.
|
||||||
|
@ -160,3 +162,71 @@ func (r *Admin) PerformAdminEvacuateRoom(
|
||||||
inputRes := &api.InputRoomEventsResponse{}
|
inputRes := &api.InputRoomEventsResponse{}
|
||||||
r.Inputer.InputRoomEvents(ctx, inputReq, inputRes)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ const (
|
||||||
RoomserverPerformInboundPeekPath = "/roomserver/performInboundPeek"
|
RoomserverPerformInboundPeekPath = "/roomserver/performInboundPeek"
|
||||||
RoomserverPerformForgetPath = "/roomserver/performForget"
|
RoomserverPerformForgetPath = "/roomserver/performForget"
|
||||||
RoomserverPerformAdminEvacuateRoomPath = "/roomserver/performAdminEvacuateRoom"
|
RoomserverPerformAdminEvacuateRoomPath = "/roomserver/performAdminEvacuateRoom"
|
||||||
|
RoomserverPerformAdminEvacuateUserPath = "/roomserver/performAdminEvacuateUser"
|
||||||
|
|
||||||
// Query operations
|
// Query operations
|
||||||
RoomserverQueryLatestEventsAndStatePath = "/roomserver/queryLatestEventsAndState"
|
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
|
// QueryLatestEventsAndState implements RoomserverQueryAPI
|
||||||
func (h *httpRoomserverInternalAPI) QueryLatestEventsAndState(
|
func (h *httpRoomserverInternalAPI) QueryLatestEventsAndState(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
|
|
@ -129,6 +129,17 @@ func AddRoutes(r api.RoomserverInternalAPI, internalAPIMux *mux.Router) {
|
||||||
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
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(
|
internalAPIMux.Handle(
|
||||||
RoomserverQueryPublishedRoomsPath,
|
RoomserverQueryPublishedRoomsPath,
|
||||||
httputil.MakeInternalAPI("queryPublishedRooms", func(req *http.Request) util.JSONResponse {
|
httputil.MakeInternalAPI("queryPublishedRooms", func(req *http.Request) util.JSONResponse {
|
||||||
|
|
|
@ -33,6 +33,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/internal/pushrules"
|
"github.com/matrix-org/dendrite/internal/pushrules"
|
||||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||||
keyapi "github.com/matrix-org/dendrite/keyserver/api"
|
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/setup/config"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/dendrite/userapi/producers"
|
"github.com/matrix-org/dendrite/userapi/producers"
|
||||||
|
@ -49,6 +50,7 @@ type UserInternalAPI struct {
|
||||||
// AppServices is the list of all registered AS
|
// AppServices is the list of all registered AS
|
||||||
AppServices []config.ApplicationService
|
AppServices []config.ApplicationService
|
||||||
KeyAPI keyapi.UserKeyAPI
|
KeyAPI keyapi.UserKeyAPI
|
||||||
|
RSAPI rsapi.UserRoomserverAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *UserInternalAPI) InputAccountData(ctx context.Context, req *api.InputAccountDataRequest, res *api.InputAccountDataResponse) error {
|
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.
|
// 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 {
|
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)
|
err := a.DB.DeactivateAccount(ctx, req.Localpart)
|
||||||
res.AccountDeactivated = err == nil
|
res.AccountDeactivated = err == nil
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -78,6 +78,7 @@ func NewInternalAPI(
|
||||||
ServerName: cfg.Matrix.ServerName,
|
ServerName: cfg.Matrix.ServerName,
|
||||||
AppServices: appServices,
|
AppServices: appServices,
|
||||||
KeyAPI: keyAPI,
|
KeyAPI: keyAPI,
|
||||||
|
RSAPI: rsAPI,
|
||||||
DisableTLSValidation: cfg.PushGatewayDisableTLSValidation,
|
DisableTLSValidation: cfg.PushGatewayDisableTLSValidation,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue