[MM-25648] api4: add user/bot convert endpoints (#14877)
* api4: add user/bot convert endpoints * api4: add convert user/bot to local mode * api4: fix linting issues * api4/bot: reflect review comments * api4: update convert user endpoint paths * remove shadow decl * fix translation problems
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
cb89c53dcc
Коммит
6a4e3293f8
44
api4/bot.go
44
api4/bot.go
@@ -21,6 +21,7 @@ func (api *API) InitBot() {
|
||||
api.BaseRoutes.Bots.Handle("", api.ApiSessionRequired(getBots)).Methods("GET")
|
||||
api.BaseRoutes.Bot.Handle("/disable", api.ApiSessionRequired(disableBot)).Methods("POST")
|
||||
api.BaseRoutes.Bot.Handle("/enable", api.ApiSessionRequired(enableBot)).Methods("POST")
|
||||
api.BaseRoutes.Bot.Handle("/convert_to_user", api.ApiSessionRequired(convertBotToUser)).Methods("POST")
|
||||
api.BaseRoutes.Bot.Handle("/assign/{user_id:[A-Za-z0-9]+}", api.ApiSessionRequired(assignBot)).Methods("POST")
|
||||
|
||||
api.BaseRoutes.Bot.Handle("/icon", api.ApiSessionRequiredTrustRequester(getBotIconImage)).Methods("GET")
|
||||
@@ -378,3 +379,46 @@ func deleteBotIconImage(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func convertBotToUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireBotUserId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
bot, err := c.App.GetBot(c.Params.BotUserId, false)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
userPatch := model.UserPatchFromJson(r.Body)
|
||||
if userPatch == nil || userPatch.Password == nil || *userPatch.Password == "" {
|
||||
c.SetInvalidParam("userPatch")
|
||||
return
|
||||
}
|
||||
|
||||
systemAdmin, _ := strconv.ParseBool(r.URL.Query().Get("set_system_admin"))
|
||||
|
||||
auditRec := c.MakeAuditRecord("convertBotToUser", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
auditRec.AddMeta("bot", bot)
|
||||
auditRec.AddMeta("userPatch", userPatch)
|
||||
auditRec.AddMeta("set_system_admin", systemAdmin)
|
||||
|
||||
if c.Params.UserId != c.App.Session().UserId && !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
||||
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := c.App.ConvertBotToUser(bot, userPatch, systemAdmin)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
auditRec.AddMeta("convertedTo", user)
|
||||
|
||||
w.Write([]byte(user.ToJson()))
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
package api4
|
||||
|
||||
func (api *API) InitBotLocal() {
|
||||
api.BaseRoutes.Bot.Handle("", api.ApiLocal(getBot)).Methods("GET")
|
||||
api.BaseRoutes.Bot.Handle("", api.ApiLocal(patchBot)).Methods("PUT")
|
||||
api.BaseRoutes.Bot.Handle("/disable", api.ApiLocal(disableBot)).Methods("POST")
|
||||
api.BaseRoutes.Bot.Handle("/enable", api.ApiLocal(enableBot)).Methods("POST")
|
||||
api.BaseRoutes.Bot.Handle("/convert_to_user", api.ApiLocal(convertBotToUser)).Methods("POST")
|
||||
api.BaseRoutes.Bot.Handle("/assign/{user_id:[A-Za-z0-9]+}", api.ApiLocal(assignBot)).Methods("POST")
|
||||
|
||||
api.BaseRoutes.Bots.Handle("", api.ApiLocal(getBots)).Methods("GET")
|
||||
|
||||
@@ -1403,6 +1403,68 @@ func TestDeleteBotIconImage(t *testing.T) {
|
||||
require.False(t, exists, "icon.svg should not for the user")
|
||||
}
|
||||
|
||||
func TestConvertBotToUser(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.TEAM_USER_ROLE_ID)
|
||||
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.EnableBotAccountCreation = true
|
||||
})
|
||||
|
||||
bot := &model.Bot{
|
||||
Username: GenerateTestUsername(),
|
||||
Description: "bot",
|
||||
}
|
||||
bot, resp := th.Client.CreateBot(bot)
|
||||
CheckCreatedStatus(t, resp)
|
||||
defer th.App.PermanentDeleteBot(bot.UserId)
|
||||
|
||||
_, resp = th.Client.ConvertBotToUser(bot.UserId, &model.UserPatch{}, false)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
user, resp := th.Client.ConvertBotToUser(bot.UserId, &model.UserPatch{Password: model.NewString("password")}, false)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
require.Nil(t, user)
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
bot := &model.Bot{
|
||||
Username: GenerateTestUsername(),
|
||||
Description: "bot",
|
||||
}
|
||||
bot, resp := th.SystemAdminClient.CreateBot(bot)
|
||||
CheckCreatedStatus(t, resp)
|
||||
|
||||
user, resp := client.ConvertBotToUser(bot.UserId, &model.UserPatch{}, false)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
user, resp = client.ConvertBotToUser(bot.UserId, &model.UserPatch{Password: model.NewString("password")}, false)
|
||||
CheckNoError(t, resp)
|
||||
require.NotNil(t, user)
|
||||
require.Equal(t, bot.UserId, user.Id)
|
||||
|
||||
bot, resp = client.GetBot(bot.UserId, "")
|
||||
CheckNotFoundStatus(t, resp)
|
||||
|
||||
bot = &model.Bot{
|
||||
Username: GenerateTestUsername(),
|
||||
Description: "systemAdminBot",
|
||||
}
|
||||
bot, resp = th.SystemAdminClient.CreateBot(bot)
|
||||
CheckCreatedStatus(t, resp)
|
||||
|
||||
user, resp = client.ConvertBotToUser(bot.UserId, &model.UserPatch{Password: model.NewString("password")}, true)
|
||||
CheckNoError(t, resp)
|
||||
require.NotNil(t, user)
|
||||
require.Equal(t, bot.UserId, user.Id)
|
||||
require.Contains(t, user.GetRoles(), model.SYSTEM_ADMIN_ROLE_ID)
|
||||
|
||||
bot, resp = client.GetBot(bot.UserId, "")
|
||||
CheckNotFoundStatus(t, resp)
|
||||
})
|
||||
}
|
||||
|
||||
func sToP(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
34
api4/user.go
34
api4/user.go
@@ -46,6 +46,7 @@ func (api *API) InitUser() {
|
||||
api.BaseRoutes.User.Handle("/password", api.ApiSessionRequired(updatePassword)).Methods("PUT")
|
||||
api.BaseRoutes.User.Handle("/promote", api.ApiSessionRequired(promoteGuestToUser)).Methods("POST")
|
||||
api.BaseRoutes.User.Handle("/demote", api.ApiSessionRequired(demoteUserToGuest)).Methods("POST")
|
||||
api.BaseRoutes.User.Handle("/convert_to_bot", api.ApiSessionRequired(convertUserToBot)).Methods("POST")
|
||||
api.BaseRoutes.Users.Handle("/password/reset", api.ApiHandler(resetPassword)).Methods("POST")
|
||||
api.BaseRoutes.Users.Handle("/password/reset/send", api.ApiHandler(sendPasswordReset)).Methods("POST")
|
||||
api.BaseRoutes.Users.Handle("/email/verify", api.ApiHandler(verifyUserEmail)).Methods("POST")
|
||||
@@ -2485,3 +2486,36 @@ func verifyUserEmailWithoutToken(c *Context, w http.ResponseWriter, r *http.Requ
|
||||
|
||||
w.Write([]byte(user.ToJson()))
|
||||
}
|
||||
|
||||
func convertUserToBot(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireUserId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := c.App.GetUser(c.Params.UserId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("convertUserToBot", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
auditRec.AddMeta("user", user)
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
||||
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||
return
|
||||
}
|
||||
|
||||
bot, err := c.App.ConvertUserToBot(user)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
auditRec.AddMeta("convertedTo", bot)
|
||||
|
||||
w.Write(bot.ToJson())
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ func (api *API) InitUserLocal() {
|
||||
api.BaseRoutes.User.Handle("/roles", api.ApiLocal(updateUserRoles)).Methods("PUT")
|
||||
api.BaseRoutes.User.Handle("/mfa", api.ApiLocal(updateUserMfa)).Methods("PUT")
|
||||
api.BaseRoutes.User.Handle("/active", api.ApiLocal(updateUserActive)).Methods("PUT")
|
||||
api.BaseRoutes.User.Handle("/convert_to_bot", api.ApiLocal(convertUserToBot)).Methods("POST")
|
||||
|
||||
api.BaseRoutes.UserByUsername.Handle("", api.ApiLocal(localGetUserByUsername)).Methods("GET")
|
||||
api.BaseRoutes.UserByEmail.Handle("", api.ApiLocal(localGetUserByEmail)).Methods("GET")
|
||||
|
||||
@@ -5075,3 +5075,29 @@ func TestPublishUserTyping(t *testing.T) {
|
||||
CheckServiceUnavailableStatus(t, resp)
|
||||
})
|
||||
}
|
||||
|
||||
func TestConvertUserToBot(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
bot, resp := th.Client.ConvertUserToBot(th.BasicUser.Id)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
require.Nil(t, bot)
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
user := model.User{Email: th.GenerateTestEmail(), Username: GenerateTestUsername(), Password: "password"}
|
||||
|
||||
ruser, resp := client.CreateUser(&user)
|
||||
CheckNoError(t, resp)
|
||||
CheckCreatedStatus(t, resp)
|
||||
|
||||
bot, resp = client.ConvertUserToBot(ruser.Id)
|
||||
CheckNoError(t, resp)
|
||||
require.NotNil(t, bot)
|
||||
require.Equal(t, bot.UserId, ruser.Id)
|
||||
|
||||
bot, resp = client.GetBot(bot.UserId, "")
|
||||
CheckNoError(t, resp)
|
||||
require.NotNil(t, bot)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -61,6 +61,8 @@ type AppIface interface {
|
||||
ChannelMembersMinusGroupMembers(channelID string, groupIDs []string, page, perPage int) ([]*model.UserWithGroups, int64, *model.AppError)
|
||||
// ClientConfigWithComputed gets the configuration in a format suitable for sending to the client.
|
||||
ClientConfigWithComputed() map[string]string
|
||||
// ConvertBotToUser converts a bot to user.
|
||||
ConvertBotToUser(bot *model.Bot, userPatch *model.UserPatch, sysadmin bool) (*model.User, *model.AppError)
|
||||
// ConvertUserToBot converts a user to bot.
|
||||
ConvertUserToBot(user *model.User) (*model.Bot, *model.AppError)
|
||||
// CreateBot creates the given bot and corresponding user.
|
||||
|
||||
@@ -1362,6 +1362,28 @@ func (a *OpenTracingAppLayer) Config() *model.Config {
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) ConvertBotToUser(bot *model.Bot, userPatch *model.UserPatch, sysadmin bool) (*model.User, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.ConvertBotToUser")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.ConvertBotToUser(bot, userPatch, sysadmin)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) ConvertUserToBot(user *model.User) (*model.Bot, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.ConvertUserToBot")
|
||||
|
||||
37
app/user.go
37
app/user.go
@@ -2140,3 +2140,40 @@ func (a *App) invalidateUserCacheAndPublish(userId string) {
|
||||
func (a *App) GetKnownUsers(userID string) ([]string, *model.AppError) {
|
||||
return a.Srv().Store.User().GetKnownUsers(userID)
|
||||
}
|
||||
|
||||
// ConvertBotToUser converts a bot to user.
|
||||
func (a *App) ConvertBotToUser(bot *model.Bot, userPatch *model.UserPatch, sysadmin bool) (*model.User, *model.AppError) {
|
||||
user, err := a.Srv().Store.User().Get(bot.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sysadmin && !user.IsInRole(model.SYSTEM_ADMIN_ROLE_ID) {
|
||||
_, err = a.UpdateUserRoles(
|
||||
user.Id,
|
||||
fmt.Sprintf("%s %s", user.Roles, model.SYSTEM_ADMIN_ROLE_ID),
|
||||
false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
user.Patch(userPatch)
|
||||
|
||||
user, err = a.UpdateUser(user, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = a.UpdatePassword(user, *userPatch.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
appErr := a.Srv().Store.Bot().PermanentDelete(bot.UserId)
|
||||
if appErr != nil {
|
||||
return nil, model.NewAppError("ConvertBotToUser", "", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
[
|
||||
{
|
||||
"id": "",
|
||||
"translation": ""
|
||||
},
|
||||
{
|
||||
"id": "April",
|
||||
"translation": "April"
|
||||
|
||||
@@ -1226,6 +1226,30 @@ func (c *Client4) DeleteUser(userId string) (bool, *Response) {
|
||||
return CheckStatusOK(r), BuildResponse(r)
|
||||
}
|
||||
|
||||
// ConvertUserToBot converts a user to a bot user.
|
||||
func (c *Client4) ConvertUserToBot(userId string) (*Bot, *Response) {
|
||||
r, err := c.DoApiPost(c.GetUserRoute(userId)+"/convert_to_bot", "")
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
return BotFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
|
||||
// ConvertBotToUser converts a bot user to a user.
|
||||
func (c *Client4) ConvertBotToUser(userId string, userPatch *UserPatch, setSystemAdmin bool) (*User, *Response) {
|
||||
var query string
|
||||
if setSystemAdmin {
|
||||
query = "?set_system_admin=true"
|
||||
}
|
||||
r, err := c.DoApiPost(c.GetBotRoute(userId)+"/convert_to_user"+query, userPatch.ToJson())
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
return UserFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
|
||||
// PermanentDeleteAll permanently deletes all users in the system. This is a local only endpoint
|
||||
func (c *Client4) PermanentDeleteAllUsers() (bool, *Response) {
|
||||
r, err := c.DoApiDelete(c.GetUsersRoute())
|
||||
|
||||
Ссылка в новой задаче
Block a user