From 225565f41214b4c5049f176592db4d6dc1371352 Mon Sep 17 00:00:00 2001 From: Ben Schumacher Date: Thu, 12 Aug 2021 00:27:35 +0200 Subject: [PATCH] [MM-37716] Drop support for LHS specific bot icons (#18087) --- api4/bot.go | 129 ----------------- api4/bot_test.go | 202 --------------------------- app/app_iface.go | 8 -- app/bot.go | 115 --------------- app/bot_test.go | 166 ---------------------- app/opentracing/opentracing_layer.go | 88 ------------ app/plugin_api.go | 20 --- i18n/en.json | 36 ----- model/client4.go | 66 --------- plugin/api.go | 19 --- plugin/api_timer_layer_generated.go | 21 --- plugin/client_rpc_generated.go | 86 ------------ plugin/plugintest/api.go | 57 -------- 13 files changed, 1013 deletions(-) diff --git a/api4/bot.go b/api4/bot.go index 01fbf4be52..2f71373007 100644 --- a/api4/bot.go +++ b/api4/bot.go @@ -5,9 +5,6 @@ package api4 import ( "encoding/json" - "fmt" - "io" - "io/ioutil" "net/http" "strconv" @@ -25,10 +22,6 @@ func (api *API) InitBot() { 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") - api.BaseRoutes.Bot.Handle("/icon", api.ApiSessionRequired(setBotIconImage)).Methods("POST") - api.BaseRoutes.Bot.Handle("/icon", api.ApiSessionRequired(deleteBotIconImage)).Methods("DELETE") } func createBot(c *Context, w http.ResponseWriter, r *http.Request) { @@ -274,128 +267,6 @@ func assignBot(c *Context, w http.ResponseWriter, _ *http.Request) { } } -func getBotIconImage(c *Context, w http.ResponseWriter, r *http.Request) { - c.RequireBotUserId() - if c.Err != nil { - return - } - botUserId := c.Params.BotUserId - - canSee, err := c.App.UserCanSeeOtherUser(c.AppContext.Session().UserId, botUserId) - if err != nil { - c.Err = err - return - } - - if !canSee { - c.SetPermissionError(model.PermissionViewMembers) - return - } - - img, err := c.App.GetBotIconImage(botUserId) - if err != nil { - c.Err = err - return - } - - user, err := c.App.GetUser(botUserId) - if err != nil { - c.Err = err - return - } - - etag := strconv.FormatInt(user.LastPictureUpdate, 10) - if c.HandleEtag(etag, "Get Icon Image", w, r) { - return - } - - w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%v, private", 24*60*60)) // 24 hrs - w.Header().Set(model.HeaderEtagServer, etag) - w.Header().Set("Content-Type", "image/svg+xml") - w.Write(img) -} - -func setBotIconImage(c *Context, w http.ResponseWriter, r *http.Request) { - defer io.Copy(ioutil.Discard, r.Body) - - c.RequireBotUserId() - if c.Err != nil { - return - } - botUserId := c.Params.BotUserId - - auditRec := c.MakeAuditRecord("setBotIconImage", audit.Fail) - defer c.LogAuditRec(auditRec) - auditRec.AddMeta("bot_id", botUserId) - - if err := c.App.SessionHasPermissionToManageBot(*c.AppContext.Session(), botUserId); err != nil { - c.Err = err - return - } - - if r.ContentLength > *c.App.Config().FileSettings.MaxFileSize { - c.Err = model.NewAppError("setBotIconImage", "api.bot.set_bot_icon_image.too_large.app_error", nil, "", http.StatusRequestEntityTooLarge) - return - } - - if err := r.ParseMultipartForm(*c.App.Config().FileSettings.MaxFileSize); err != nil { - c.Err = model.NewAppError("setBotIconImage", "api.bot.set_bot_icon_image.parse.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } - - m := r.MultipartForm - imageArray, ok := m.File["image"] - if !ok { - c.Err = model.NewAppError("setBotIconImage", "api.bot.set_bot_icon_image.no_file.app_error", nil, "", http.StatusBadRequest) - return - } - - if len(imageArray) <= 0 { - c.Err = model.NewAppError("setBotIconImage", "api.bot.set_bot_icon_image.array.app_error", nil, "", http.StatusBadRequest) - return - } - - imageData := imageArray[0] - if err := c.App.SetBotIconImageFromMultiPartFile(botUserId, imageData); err != nil { - c.Err = err - return - } - - auditRec.Success() - c.LogAudit("") - - ReturnStatusOK(w) -} - -func deleteBotIconImage(c *Context, w http.ResponseWriter, r *http.Request) { - defer io.Copy(ioutil.Discard, r.Body) - - c.RequireBotUserId() - if c.Err != nil { - return - } - botUserId := c.Params.BotUserId - - auditRec := c.MakeAuditRecord("deleteBotIconImage", audit.Fail) - defer c.LogAuditRec(auditRec) - auditRec.AddMeta("bot_id", botUserId) - - if err := c.App.SessionHasPermissionToManageBot(*c.AppContext.Session(), botUserId); err != nil { - c.Err = err - return - } - - if err := c.App.DeleteBotIconImage(botUserId); err != nil { - c.Err = err - return - } - - auditRec.Success() - c.LogAudit("") - - ReturnStatusOK(w) -} - func convertBotToUser(c *Context, w http.ResponseWriter, r *http.Request) { c.RequireBotUserId() if c.Err != nil { diff --git a/api4/bot_test.go b/api4/bot_test.go index a66b175eef..470e0a75f5 100644 --- a/api4/bot_test.go +++ b/api4/bot_test.go @@ -5,19 +5,13 @@ package api4 import ( "encoding/json" - "fmt" "io/ioutil" - "net/http" - "os" - "path/filepath" "strings" "testing" "github.com/stretchr/testify/require" "github.com/mattermost/mattermost-server/v6/model" - "github.com/mattermost/mattermost-server/v6/utils/fileutils" - "github.com/mattermost/mattermost-server/v6/utils/testutils" ) func TestCreateBot(t *testing.T) { @@ -1212,202 +1206,6 @@ func TestAssignBot(t *testing.T) { }) } -func TestSetBotIconImage(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - user := th.BasicUser - - defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions()) - - th.AddPermissionToRole(model.PermissionCreateBot.Id, model.SystemUserRoleId) - th.AddPermissionToRole(model.PermissionManageBots.Id, model.SystemUserRoleId) - th.AddPermissionToRole(model.PermissionReadBots.Id, model.SystemUserRoleId) - 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) - - badData, err := testutils.ReadTestFile("test.png") - require.NoError(t, err) - - goodData, err := testutils.ReadTestFile("test.svg") - require.NoError(t, err) - - // SetBotIconImage only allowed for bots - _, resp = th.SystemAdminClient.SetBotIconImage(user.Id, goodData) - CheckNotFoundStatus(t, resp) - - // png/jpg is not allowed - ok, resp := th.Client.SetBotIconImage(bot.UserId, badData) - require.False(t, ok, "Should return false, set icon image only allows svg") - CheckBadRequestStatus(t, resp) - - ok, resp = th.Client.SetBotIconImage(model.NewId(), badData) - require.False(t, ok, "Should return false, set icon image not allowed") - CheckNotFoundStatus(t, resp) - - _, resp = th.Client.SetBotIconImage(bot.UserId, goodData) - CheckNoError(t, resp) - - // status code returns either forbidden or unauthorized - // note: forbidden is set as default at Client4.SetBotIconImage when request is terminated early by server - th.Client.Logout() - _, resp = th.Client.SetBotIconImage(bot.UserId, badData) - if resp.StatusCode == http.StatusForbidden { - CheckForbiddenStatus(t, resp) - } else if resp.StatusCode == http.StatusUnauthorized { - CheckUnauthorizedStatus(t, resp) - } else { - require.Fail(t, "Should have failed either forbidden or unauthorized") - } - - _, resp = th.SystemAdminClient.SetBotIconImage(bot.UserId, goodData) - CheckNoError(t, resp) - - fpath := fmt.Sprintf("/bots/%v/icon.svg", bot.UserId) - actualData, appErr := th.App.ReadFile(fpath) - require.Nil(t, appErr) - require.NotNil(t, actualData) - require.Equal(t, goodData, actualData) - - info := &model.FileInfo{Path: fpath} - err = th.cleanupTestFile(info) - require.NoError(t, err) -} - -func TestGetBotIconImage(t *testing.T) { - th := Setup(t) - defer th.TearDown() - - defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions()) - - th.AddPermissionToRole(model.PermissionCreateBot.Id, model.SystemUserRoleId) - th.AddPermissionToRole(model.PermissionManageBots.Id, model.SystemUserRoleId) - th.AddPermissionToRole(model.PermissionReadBots.Id, model.SystemUserRoleId) - 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) - - // Get icon image for user with no icon - data, resp := th.Client.GetBotIconImage(bot.UserId) - CheckNotFoundStatus(t, resp) - require.Equal(t, 0, len(data)) - - // Set an icon image - path, _ := fileutils.FindDir("tests") - svgFile, fileErr := os.Open(filepath.Join(path, "test.svg")) - require.NoError(t, fileErr) - defer svgFile.Close() - - expectedData, err := ioutil.ReadAll(svgFile) - require.NoError(t, err) - - svgFile.Seek(0, 0) - fpath := fmt.Sprintf("/bots/%v/icon.svg", bot.UserId) - _, appErr := th.App.WriteFile(svgFile, fpath) - require.Nil(t, appErr) - - data, resp = th.Client.GetBotIconImage(bot.UserId) - CheckNoError(t, resp) - require.Equal(t, expectedData, data) - - _, resp = th.Client.GetBotIconImage("junk") - CheckBadRequestStatus(t, resp) - - _, resp = th.Client.GetBotIconImage(model.NewId()) - CheckNotFoundStatus(t, resp) - - th.Client.Logout() - _, resp = th.Client.GetBotIconImage(bot.UserId) - CheckUnauthorizedStatus(t, resp) - - _, resp = th.SystemAdminClient.GetBotIconImage(bot.UserId) - CheckNoError(t, resp) - - info := &model.FileInfo{Path: "/bots/" + bot.UserId + "/icon.svg"} - err = th.cleanupTestFile(info) - require.NoError(t, err) -} - -func TestDeleteBotIconImage(t *testing.T) { - th := Setup(t) - defer th.TearDown() - - defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions()) - - th.AddPermissionToRole(model.PermissionCreateBot.Id, model.SystemUserRoleId) - th.AddPermissionToRole(model.PermissionManageBots.Id, model.SystemUserRoleId) - th.AddPermissionToRole(model.PermissionReadBots.Id, model.SystemUserRoleId) - 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) - - // Get icon image for user with no icon - data, resp := th.Client.GetBotIconImage(bot.UserId) - CheckNotFoundStatus(t, resp) - require.Equal(t, 0, len(data)) - - // Set an icon image - svgData, err := testutils.ReadTestFile("test.svg") - require.NoError(t, err) - - _, resp = th.Client.SetBotIconImage(bot.UserId, svgData) - CheckNoError(t, resp) - - fpath := fmt.Sprintf("/bots/%v/icon.svg", bot.UserId) - exists, appErr := th.App.FileExists(fpath) - require.Nil(t, appErr) - require.True(t, exists, "icon.svg needs to exist for the user") - - data, resp = th.Client.GetBotIconImage(bot.UserId) - CheckNoError(t, resp) - require.Equal(t, svgData, data) - - success, resp := th.Client.DeleteBotIconImage("junk") - CheckBadRequestStatus(t, resp) - require.False(t, success) - - success, resp = th.Client.DeleteBotIconImage(model.NewId()) - CheckNotFoundStatus(t, resp) - require.False(t, success) - - success, resp = th.Client.DeleteBotIconImage(bot.UserId) - CheckNoError(t, resp) - require.True(t, success) - - th.Client.Logout() - success, resp = th.Client.DeleteBotIconImage(bot.UserId) - CheckUnauthorizedStatus(t, resp) - require.False(t, success) - - exists, appErr = th.App.FileExists(fpath) - require.Nil(t, appErr) - require.False(t, exists, "icon.svg should not for the user") -} - func TestConvertBotToUser(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() diff --git a/app/app_iface.go b/app/app_iface.go index a291fdc2f6..b3826358c3 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -108,8 +108,6 @@ type AppIface interface { // ['town-square', 'game-of-thrones', 'wow'] // DefaultChannelNames() []string - // DeleteBotIconImage deletes LHS icon for a bot. - DeleteBotIconImage(botUserId string) *model.AppError // DeleteChannelScheme deletes a channels scheme and sets its SchemeId to nil. DeleteChannelScheme(channel *model.Channel) (*model.Channel, *model.AppError) // DeleteGroupConstrainedMemberships deletes team and channel memberships of users who aren't members of the allowed @@ -154,8 +152,6 @@ type AppIface interface { GetAllLdapGroupsPage(page int, perPage int, opts model.LdapGroupSearchOpts) ([]*model.Group, int, *model.AppError) // GetBot returns the given bot. GetBot(botUserId string, includeDeleted bool) (*model.Bot, *model.AppError) - // GetBotIconImage retrieves LHS icon for a bot. - GetBotIconImage(botUserId string) ([]byte, *model.AppError) // GetBots returns the requested page of bots. GetBots(options *model.BotGetOptions) (model.BotList, *model.AppError) // GetChannelGroupUsers returns the users who are associated to the channel via GroupChannels and GroupMembers. @@ -299,10 +295,6 @@ type AppIface interface { SessionHasPermissionToManageBot(session model.Session, botUserId string) *model.AppError // SessionIsRegistered determines if a specific session has been registered SessionIsRegistered(session model.Session) bool - // SetBotIconImage sets LHS icon for a bot. - SetBotIconImage(botUserId string, file io.ReadSeeker) *model.AppError - // SetBotIconImageFromMultiPartFile sets LHS icon for a bot. - SetBotIconImageFromMultiPartFile(botUserId string, imageData *multipart.FileHeader) *model.AppError // SetSessionExpireInDays sets the session's expiry the specified number of days // relative to either the session creation date or the current time, depending // on the `ExtendSessionOnActivity` config setting. diff --git a/app/bot.go b/app/bot.go index 17219d1fd7..e54476c2f3 100644 --- a/app/bot.go +++ b/app/bot.go @@ -4,17 +4,11 @@ package app import ( - "bytes" "context" "errors" "fmt" - "io" - "io/ioutil" - "mime/multipart" "net/http" - "path/filepath" - "github.com/mattermost/mattermost-server/v6/app/imaging" "github.com/mattermost/mattermost-server/v6/app/request" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/shared/i18n" @@ -571,112 +565,3 @@ func (a *App) ConvertUserToBot(user *model.User) (*model.Bot, *model.AppError) { } return bot, nil } - -// SetBotIconImageFromMultiPartFile sets LHS icon for a bot. -func (a *App) SetBotIconImageFromMultiPartFile(botUserId string, imageData *multipart.FileHeader) *model.AppError { - file, err := imageData.Open() - if err != nil { - return model.NewAppError("SetBotIconImage", "api.bot.set_bot_icon_image.open.app_error", nil, err.Error(), http.StatusBadRequest) - } - defer file.Close() - - file.Seek(0, 0) - return a.SetBotIconImage(botUserId, file) -} - -// SetBotIconImage sets LHS icon for a bot. -func (a *App) SetBotIconImage(botUserId string, file io.ReadSeeker) *model.AppError { - bot, err := a.GetBot(botUserId, true) - if err != nil { - return err - } - - if _, err := imaging.ParseSVG(file); err != nil { - return model.NewAppError("SetBotIconImage", "api.bot.set_bot_icon_image.parse.app_error", nil, err.Error(), http.StatusBadRequest) - } - - file.Seek(0, 0) - data, readErr := ioutil.ReadAll(file) - if readErr != nil { - return model.NewAppError("SetBotIconImage", "api.bot.set_bot_icon_image.read.app_error", nil, readErr.Error(), http.StatusInternalServerError) - } - - if storedData, readFileErr := a.ReadFile(getBotIconPath(botUserId)); readFileErr == nil && bytes.Equal(storedData, data) { - return nil - } - - // Set icon - if _, err = a.WriteFile(bytes.NewReader(data), getBotIconPath(botUserId)); err != nil { - return model.NewAppError("SetBotIconImage", "api.bot.set_bot_icon_image.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - bot.LastIconUpdate = model.GetMillis() - if _, err := a.Srv().Store.Bot().Update(bot); err != nil { - var nfErr *store.ErrNotFound - var appErr *model.AppError - switch { - case errors.As(err, &nfErr): - return model.MakeBotNotFoundError(nfErr.ID) - case errors.As(err, &appErr): // in case we haven't converted to plain error. - return appErr - default: // last fallback in case it doesn't map to an existing app error. - return model.NewAppError("SetBotIconImage", "app.bot.patchbot.internal_error", nil, err.Error(), http.StatusInternalServerError) - } - } - a.invalidateUserCacheAndPublish(botUserId) - - return nil -} - -// DeleteBotIconImage deletes LHS icon for a bot. -func (a *App) DeleteBotIconImage(botUserId string) *model.AppError { - bot, err := a.GetBot(botUserId, true) - if err != nil { - return err - } - - // Delete icon - if err = a.RemoveFile(getBotIconPath(botUserId)); err != nil { - return model.NewAppError("DeleteBotIconImage", "api.bot.delete_bot_icon_image.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - if nErr := a.Srv().Store.User().UpdateLastPictureUpdate(botUserId); nErr != nil { - mlog.Warn(nErr.Error()) - } - - bot.LastIconUpdate = int64(0) - if _, err := a.Srv().Store.Bot().Update(bot); err != nil { - var nfErr *store.ErrNotFound - var appErr *model.AppError - switch { - case errors.As(err, &nfErr): - return model.MakeBotNotFoundError(nfErr.ID) - case errors.As(err, &appErr): // in case we haven't converted to plain error. - return appErr - default: // last fallback in case it doesn't map to an existing app error. - return model.NewAppError("DeleteBotIconImage", "app.bot.patchbot.internal_error", nil, err.Error(), http.StatusInternalServerError) - } - } - - a.invalidateUserCacheAndPublish(botUserId) - - return nil -} - -// GetBotIconImage retrieves LHS icon for a bot. -func (a *App) GetBotIconImage(botUserId string) ([]byte, *model.AppError) { - if _, err := a.GetBot(botUserId, true); err != nil { - return nil, err - } - - data, err := a.ReadFile(getBotIconPath(botUserId)) - if err != nil { - return nil, model.NewAppError("GetBotIconImage", "api.bot.get_bot_icon_image.read.app_error", nil, err.Error(), http.StatusNotFound) - } - - return data, nil -} - -func getBotIconPath(botUserId string) string { - return filepath.Join("bots", botUserId, "icon.svg") -} diff --git a/app/bot_test.go b/app/bot_test.go index 65132fe77b..cd27ebf623 100644 --- a/app/bot_test.go +++ b/app/bot_test.go @@ -5,9 +5,6 @@ package app import ( "fmt" - "io/ioutil" - "os" - "path/filepath" "strings" "testing" @@ -15,7 +12,6 @@ import ( "github.com/stretchr/testify/require" "github.com/mattermost/mattermost-server/v6/model" - "github.com/mattermost/mattermost-server/v6/utils/fileutils" ) func TestCreateBot(t *testing.T) { @@ -753,168 +749,6 @@ func TestConvertUserToBot(t *testing.T) { }) } -func TestSetBotIconImage(t *testing.T) { - t.Run("invalid bot", func(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - path, _ := fileutils.FindDir("tests") - svgFile, fileErr := os.Open(filepath.Join(path, "test.svg")) - require.NoError(t, fileErr) - defer svgFile.Close() - - err := th.App.SetBotIconImage("invalid_bot_id", svgFile) - require.NotNil(t, err) - }) - - t.Run("valid bot", func(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - // Set an icon image - path, _ := fileutils.FindDir("tests") - svgFile, fileErr := os.Open(filepath.Join(path, "test.svg")) - require.NoError(t, fileErr) - defer svgFile.Close() - - expectedData, fileErr := ioutil.ReadAll(svgFile) - require.NoError(t, fileErr) - require.NotNil(t, expectedData) - - bot, err := th.App.ConvertUserToBot(&model.User{ - Username: "username", - Id: th.BasicUser.Id, - }) - require.Nil(t, err) - defer th.App.PermanentDeleteBot(bot.UserId) - - fpath := fmt.Sprintf("/bots/%v/icon.svg", bot.UserId) - exists, err := th.App.FileExists(fpath) - require.Nil(t, err) - require.False(t, exists, "icon.svg shouldn't exist for the bot") - - svgFile.Seek(0, 0) - err = th.App.SetBotIconImage(bot.UserId, svgFile) - require.Nil(t, err) - - exists, err = th.App.FileExists(fpath) - require.Nil(t, err) - require.True(t, exists, "icon.svg should exist for the bot") - - actualData, err := th.App.ReadFile(fpath) - require.Nil(t, err) - require.NotNil(t, actualData) - - require.Equal(t, expectedData, actualData) - }) -} - -func TestGetBotIconImage(t *testing.T) { - t.Run("invalid bot", func(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - actualData, err := th.App.GetBotIconImage("invalid_bot_id") - require.NotNil(t, err) - require.Nil(t, actualData) - }) - - t.Run("valid bot", func(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - // Set an icon image - path, _ := fileutils.FindDir("tests") - svgFile, fileErr := os.Open(filepath.Join(path, "test.svg")) - require.NoError(t, fileErr) - defer svgFile.Close() - - expectedData, fileErr := ioutil.ReadAll(svgFile) - require.NoError(t, fileErr) - require.NotNil(t, expectedData) - - bot, err := th.App.ConvertUserToBot(&model.User{ - Username: "username", - Id: th.BasicUser.Id, - }) - require.Nil(t, err) - defer th.App.PermanentDeleteBot(bot.UserId) - - svgFile.Seek(0, 0) - fpath := fmt.Sprintf("/bots/%v/icon.svg", bot.UserId) - _, err = th.App.WriteFile(svgFile, fpath) - require.Nil(t, err) - - actualBytes, err := th.App.GetBotIconImage(bot.UserId) - require.Nil(t, err) - require.NotNil(t, actualBytes) - - actualData, err := th.App.ReadFile(fpath) - require.Nil(t, err) - require.NotNil(t, actualData) - - require.Equal(t, expectedData, actualData) - }) -} - -func TestDeleteBotIconImage(t *testing.T) { - t.Run("invalid bot", func(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - err := th.App.DeleteBotIconImage("invalid_bot_id") - require.NotNil(t, err) - }) - - t.Run("valid bot", func(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - // Set an icon image - path, _ := fileutils.FindDir("tests") - svgFile, fileErr := os.Open(filepath.Join(path, "test.svg")) - require.NoError(t, fileErr) - defer svgFile.Close() - - expectedData, fileErr := ioutil.ReadAll(svgFile) - require.NoError(t, fileErr) - require.NotNil(t, expectedData) - - bot, err := th.App.ConvertUserToBot(&model.User{ - Username: "username", - Id: th.BasicUser.Id, - }) - require.Nil(t, err) - defer th.App.PermanentDeleteBot(bot.UserId) - - // Set icon - svgFile.Seek(0, 0) - err = th.App.SetBotIconImage(bot.UserId, svgFile) - require.Nil(t, err) - - // Get icon - actualData, err := th.App.GetBotIconImage(bot.UserId) - require.Nil(t, err) - require.NotNil(t, actualData) - require.Equal(t, expectedData, actualData) - - // Bot icon should exist - fpath := fmt.Sprintf("/bots/%v/icon.svg", bot.UserId) - exists, err := th.App.FileExists(fpath) - require.Nil(t, err) - require.True(t, exists, "icon.svg should exist for the bot") - - // Delete icon - err = th.App.DeleteBotIconImage(bot.UserId) - require.Nil(t, err) - - // Bot icon should not exist - exists, err = th.App.FileExists(fpath) - require.Nil(t, err) - require.False(t, exists, "icon.svg should be deleted for the bot") - }) -} - func TestGetSystemBot(t *testing.T) { t.Run("An error should be returned if there are no sysadmins in the instance", func(t *testing.T) { th := Setup(t).InitBasic() diff --git a/app/opentracing/opentracing_layer.go b/app/opentracing/opentracing_layer.go index 0ee47e45f8..1bfcaad994 100644 --- a/app/opentracing/opentracing_layer.go +++ b/app/opentracing/opentracing_layer.go @@ -2785,28 +2785,6 @@ func (a *OpenTracingAppLayer) DeleteAllKeysForPlugin(pluginID string) *model.App return resultVar0 } -func (a *OpenTracingAppLayer) DeleteBotIconImage(botUserId string) *model.AppError { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DeleteBotIconImage") - - a.ctx = newCtx - a.app.Srv().Store.SetContext(newCtx) - defer func() { - a.app.Srv().Store.SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0 := a.app.DeleteBotIconImage(botUserId) - - if resultVar0 != nil { - span.LogFields(spanlog.Error(resultVar0)) - ext.Error.Set(span, true) - } - - return resultVar0 -} - func (a *OpenTracingAppLayer) DeleteBrandImage() *model.AppError { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DeleteBrandImage") @@ -4659,28 +4637,6 @@ func (a *OpenTracingAppLayer) GetBot(botUserId string, includeDeleted bool) (*mo return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) GetBotIconImage(botUserId string) ([]byte, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetBotIconImage") - - 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.GetBotIconImage(botUserId) - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - func (a *OpenTracingAppLayer) GetBots(options *model.BotGetOptions) (model.BotList, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetBots") @@ -14727,50 +14683,6 @@ func (a *OpenTracingAppLayer) SetAutoResponderStatus(user *model.User, oldNotify a.app.SetAutoResponderStatus(user, oldNotifyProps) } -func (a *OpenTracingAppLayer) SetBotIconImage(botUserId string, file io.ReadSeeker) *model.AppError { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SetBotIconImage") - - a.ctx = newCtx - a.app.Srv().Store.SetContext(newCtx) - defer func() { - a.app.Srv().Store.SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0 := a.app.SetBotIconImage(botUserId, file) - - if resultVar0 != nil { - span.LogFields(spanlog.Error(resultVar0)) - ext.Error.Set(span, true) - } - - return resultVar0 -} - -func (a *OpenTracingAppLayer) SetBotIconImageFromMultiPartFile(botUserId string, imageData *multipart.FileHeader) *model.AppError { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SetBotIconImageFromMultiPartFile") - - a.ctx = newCtx - a.app.Srv().Store.SetContext(newCtx) - defer func() { - a.app.Srv().Store.SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0 := a.app.SetBotIconImageFromMultiPartFile(botUserId, imageData) - - if resultVar0 != nil { - span.LogFields(spanlog.Error(resultVar0)) - ext.Error.Set(span, true) - } - - return resultVar0 -} - func (a *OpenTracingAppLayer) SetCustomStatus(userID string, cs *model.CustomStatus) *model.AppError { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SetCustomStatus") diff --git a/app/plugin_api.go b/app/plugin_api.go index e681bf46c6..cf2e44e4a6 100644 --- a/app/plugin_api.go +++ b/app/plugin_api.go @@ -939,26 +939,6 @@ func (api *PluginAPI) PermanentDeleteBot(userID string) *model.AppError { return api.app.PermanentDeleteBot(userID) } -func (api *PluginAPI) GetBotIconImage(userID string) ([]byte, *model.AppError) { - if _, err := api.app.GetBot(userID, true); err != nil { - return nil, err - } - - return api.app.GetBotIconImage(userID) -} - -func (api *PluginAPI) SetBotIconImage(userID string, data []byte) *model.AppError { - return api.app.SetBotIconImage(userID, bytes.NewReader(data)) -} - -func (api *PluginAPI) DeleteBotIconImage(userID string) *model.AppError { - if _, err := api.app.GetBot(userID, true); err != nil { - return err - } - - return api.app.DeleteBotIconImage(userID) -} - func (api *PluginAPI) PublishUserTyping(userID, channelID, parentId string) *model.AppError { return api.app.PublishUserTyping(userID, channelID, parentId) } diff --git a/i18n/en.json b/i18n/en.json index f5f7547891..1b0c1c96de 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -183,42 +183,6 @@ "id": "api.bot.create_disabled", "translation": "Bot creation has been disabled." }, - { - "id": "api.bot.delete_bot_icon_image.app_error", - "translation": "Couldn't delete icon image." - }, - { - "id": "api.bot.get_bot_icon_image.read.app_error", - "translation": "Unable to read icon image file." - }, - { - "id": "api.bot.set_bot_icon_image.app_error", - "translation": "Couldn't upload icon image." - }, - { - "id": "api.bot.set_bot_icon_image.array.app_error", - "translation": "Empty array under 'image' in request." - }, - { - "id": "api.bot.set_bot_icon_image.no_file.app_error", - "translation": "No file under 'image' in request." - }, - { - "id": "api.bot.set_bot_icon_image.open.app_error", - "translation": "Could not open image file." - }, - { - "id": "api.bot.set_bot_icon_image.parse.app_error", - "translation": "Could not parse multipart form." - }, - { - "id": "api.bot.set_bot_icon_image.read.app_error", - "translation": "Could not read image data." - }, - { - "id": "api.bot.set_bot_icon_image.too_large.app_error", - "translation": "Unable to upload icon image. File is too large." - }, { "id": "api.bot.teams_channels.add_message_mobile", "translation": "Please add me to teams and channels you want me to interact in. To do this, use the browser or Mattermost Desktop App." diff --git a/model/client4.go b/model/client4.go index 50d1c2c016..767e8f8f3a 100644 --- a/model/client4.go +++ b/model/client4.go @@ -1858,72 +1858,6 @@ func (c *Client4) AssignBot(botUserId, newOwnerId string) (*Bot, *Response) { return bot, BuildResponse(r) } -// SetBotIconImage sets LHS bot icon image. -func (c *Client4) SetBotIconImage(botUserId string, data []byte) (bool, *Response) { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - part, err := writer.CreateFormFile("image", "icon.svg") - if err != nil { - return false, &Response{Error: NewAppError("SetBotIconImage", "model.client.set_bot_icon_image.no_file.app_error", nil, err.Error(), http.StatusBadRequest)} - } - - if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { - return false, &Response{Error: NewAppError("SetBotIconImage", "model.client.set_bot_icon_image.no_file.app_error", nil, err.Error(), http.StatusBadRequest)} - } - - if err = writer.Close(); err != nil { - return false, &Response{Error: NewAppError("SetBotIconImage", "model.client.set_bot_icon_image.writer.app_error", nil, err.Error(), http.StatusBadRequest)} - } - - rq, err := http.NewRequest("POST", c.ApiUrl+c.GetBotRoute(botUserId)+"/icon", bytes.NewReader(body.Bytes())) - if err != nil { - return false, &Response{Error: NewAppError("SetBotIconImage", "model.client.connecting.app_error", nil, err.Error(), http.StatusBadRequest)} - } - rq.Header.Set("Content-Type", writer.FormDataContentType()) - - if c.AuthToken != "" { - rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken) - } - - rp, err := c.HttpClient.Do(rq) - if err != nil || rp == nil { - return false, &Response{StatusCode: http.StatusForbidden, Error: NewAppError(c.GetBotRoute(botUserId)+"/icon", "model.client.connecting.app_error", nil, err.Error(), http.StatusForbidden)} - } - defer closeBody(rp) - - if rp.StatusCode >= 300 { - return false, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) - } - - return CheckStatusOK(rp), BuildResponse(rp) -} - -// GetBotIconImage gets LHS bot icon image. Must be logged in. -func (c *Client4) GetBotIconImage(botUserId string) ([]byte, *Response) { - r, appErr := c.DoApiGet(c.GetBotRoute(botUserId)+"/icon", "") - if appErr != nil { - return nil, BuildErrorResponse(r, appErr) - } - defer closeBody(r) - - data, err := ioutil.ReadAll(r.Body) - if err != nil { - return nil, BuildErrorResponse(r, NewAppError("GetBotIconImage", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)) - } - return data, BuildResponse(r) -} - -// DeleteBotIconImage deletes LHS bot icon image. Must be logged in. -func (c *Client4) DeleteBotIconImage(botUserId string) (bool, *Response) { - r, appErr := c.DoApiDelete(c.GetBotRoute(botUserId) + "/icon") - if appErr != nil { - return false, BuildErrorResponse(r, appErr) - } - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) -} - // Team Section // CreateTeam creates a team in the system based on the provided team struct. diff --git a/plugin/api.go b/plugin/api.go index 4c6474dbdd..107ae37ed7 100644 --- a/plugin/api.go +++ b/plugin/api.go @@ -995,25 +995,6 @@ type API interface { // Minimum server version: 5.10 PermanentDeleteBot(botUserId string) *model.AppError - // GetBotIconImage gets LHS bot icon image. - // - // @tag Bot - // Minimum server version: 5.14 - GetBotIconImage(botUserId string) ([]byte, *model.AppError) - - // SetBotIconImage sets LHS bot icon image. - // Icon image must be SVG format, all other formats are rejected. - // - // @tag Bot - // Minimum server version: 5.14 - SetBotIconImage(botUserId string, data []byte) *model.AppError - - // DeleteBotIconImage deletes LHS bot icon image. - // - // @tag Bot - // Minimum server version: 5.14 - DeleteBotIconImage(botUserId string) *model.AppError - // PluginHTTP allows inter-plugin requests to plugin APIs. // // Minimum server version: 5.18 diff --git a/plugin/api_timer_layer_generated.go b/plugin/api_timer_layer_generated.go index 5600e42fe9..17c8aaaff4 100644 --- a/plugin/api_timer_layer_generated.go +++ b/plugin/api_timer_layer_generated.go @@ -1065,27 +1065,6 @@ func (api *apiTimerLayer) PermanentDeleteBot(botUserId string) *model.AppError { return _returnsA } -func (api *apiTimerLayer) GetBotIconImage(botUserId string) ([]byte, *model.AppError) { - startTime := timePkg.Now() - _returnsA, _returnsB := api.apiImpl.GetBotIconImage(botUserId) - api.recordTime(startTime, "GetBotIconImage", _returnsB == nil) - return _returnsA, _returnsB -} - -func (api *apiTimerLayer) SetBotIconImage(botUserId string, data []byte) *model.AppError { - startTime := timePkg.Now() - _returnsA := api.apiImpl.SetBotIconImage(botUserId, data) - api.recordTime(startTime, "SetBotIconImage", _returnsA == nil) - return _returnsA -} - -func (api *apiTimerLayer) DeleteBotIconImage(botUserId string) *model.AppError { - startTime := timePkg.Now() - _returnsA := api.apiImpl.DeleteBotIconImage(botUserId) - api.recordTime(startTime, "DeleteBotIconImage", _returnsA == nil) - return _returnsA -} - func (api *apiTimerLayer) PluginHTTP(request *http.Request) *http.Response { startTime := timePkg.Now() _returnsA := api.apiImpl.PluginHTTP(request) diff --git a/plugin/client_rpc_generated.go b/plugin/client_rpc_generated.go index 515b7c2f00..a55236aea8 100644 --- a/plugin/client_rpc_generated.go +++ b/plugin/client_rpc_generated.go @@ -4765,92 +4765,6 @@ func (s *apiRPCServer) PermanentDeleteBot(args *Z_PermanentDeleteBotArgs, return return nil } -type Z_GetBotIconImageArgs struct { - A string -} - -type Z_GetBotIconImageReturns struct { - A []byte - B *model.AppError -} - -func (g *apiRPCClient) GetBotIconImage(botUserId string) ([]byte, *model.AppError) { - _args := &Z_GetBotIconImageArgs{botUserId} - _returns := &Z_GetBotIconImageReturns{} - if err := g.client.Call("Plugin.GetBotIconImage", _args, _returns); err != nil { - log.Printf("RPC call to GetBotIconImage API failed: %s", err.Error()) - } - return _returns.A, _returns.B -} - -func (s *apiRPCServer) GetBotIconImage(args *Z_GetBotIconImageArgs, returns *Z_GetBotIconImageReturns) error { - if hook, ok := s.impl.(interface { - GetBotIconImage(botUserId string) ([]byte, *model.AppError) - }); ok { - returns.A, returns.B = hook.GetBotIconImage(args.A) - } else { - return encodableError(fmt.Errorf("API GetBotIconImage called but not implemented.")) - } - return nil -} - -type Z_SetBotIconImageArgs struct { - A string - B []byte -} - -type Z_SetBotIconImageReturns struct { - A *model.AppError -} - -func (g *apiRPCClient) SetBotIconImage(botUserId string, data []byte) *model.AppError { - _args := &Z_SetBotIconImageArgs{botUserId, data} - _returns := &Z_SetBotIconImageReturns{} - if err := g.client.Call("Plugin.SetBotIconImage", _args, _returns); err != nil { - log.Printf("RPC call to SetBotIconImage API failed: %s", err.Error()) - } - return _returns.A -} - -func (s *apiRPCServer) SetBotIconImage(args *Z_SetBotIconImageArgs, returns *Z_SetBotIconImageReturns) error { - if hook, ok := s.impl.(interface { - SetBotIconImage(botUserId string, data []byte) *model.AppError - }); ok { - returns.A = hook.SetBotIconImage(args.A, args.B) - } else { - return encodableError(fmt.Errorf("API SetBotIconImage called but not implemented.")) - } - return nil -} - -type Z_DeleteBotIconImageArgs struct { - A string -} - -type Z_DeleteBotIconImageReturns struct { - A *model.AppError -} - -func (g *apiRPCClient) DeleteBotIconImage(botUserId string) *model.AppError { - _args := &Z_DeleteBotIconImageArgs{botUserId} - _returns := &Z_DeleteBotIconImageReturns{} - if err := g.client.Call("Plugin.DeleteBotIconImage", _args, _returns); err != nil { - log.Printf("RPC call to DeleteBotIconImage API failed: %s", err.Error()) - } - return _returns.A -} - -func (s *apiRPCServer) DeleteBotIconImage(args *Z_DeleteBotIconImageArgs, returns *Z_DeleteBotIconImageReturns) error { - if hook, ok := s.impl.(interface { - DeleteBotIconImage(botUserId string) *model.AppError - }); ok { - returns.A = hook.DeleteBotIconImage(args.A) - } else { - return encodableError(fmt.Errorf("API DeleteBotIconImage called but not implemented.")) - } - return nil -} - type Z_PublishUserTypingArgs struct { A string B string diff --git a/plugin/plugintest/api.go b/plugin/plugintest/api.go index 3c58c020fa..f00c38badf 100644 --- a/plugin/plugintest/api.go +++ b/plugin/plugintest/api.go @@ -416,22 +416,6 @@ func (_m *API) CreateUserAccessToken(token *model.UserAccessToken) (*model.UserA return r0, r1 } -// DeleteBotIconImage provides a mock function with given fields: botUserId -func (_m *API) DeleteBotIconImage(botUserId string) *model.AppError { - ret := _m.Called(botUserId) - - var r0 *model.AppError - if rf, ok := ret.Get(0).(func(string) *model.AppError); ok { - r0 = rf(botUserId) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*model.AppError) - } - } - - return r0 -} - // DeleteChannel provides a mock function with given fields: channelId func (_m *API) DeleteChannel(channelId string) *model.AppError { ret := _m.Called(channelId) @@ -659,31 +643,6 @@ func (_m *API) GetBot(botUserId string, includeDeleted bool) (*model.Bot, *model return r0, r1 } -// GetBotIconImage provides a mock function with given fields: botUserId -func (_m *API) GetBotIconImage(botUserId string) ([]byte, *model.AppError) { - ret := _m.Called(botUserId) - - var r0 []byte - if rf, ok := ret.Get(0).(func(string) []byte); ok { - r0 = rf(botUserId) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]byte) - } - } - - var r1 *model.AppError - if rf, ok := ret.Get(1).(func(string) *model.AppError); ok { - r1 = rf(botUserId) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(*model.AppError) - } - } - - return r0, r1 -} - // GetBots provides a mock function with given fields: options func (_m *API) GetBots(options *model.BotGetOptions) ([]*model.Bot, *model.AppError) { ret := _m.Called(options) @@ -3166,22 +3125,6 @@ func (_m *API) SendMail(to string, subject string, htmlBody string) *model.AppEr return r0 } -// SetBotIconImage provides a mock function with given fields: botUserId, data -func (_m *API) SetBotIconImage(botUserId string, data []byte) *model.AppError { - ret := _m.Called(botUserId, data) - - var r0 *model.AppError - if rf, ok := ret.Get(0).(func(string, []byte) *model.AppError); ok { - r0 = rf(botUserId, data) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*model.AppError) - } - } - - return r0 -} - // SetProfileImage provides a mock function with given fields: userID, data func (_m *API) SetProfileImage(userID string, data []byte) *model.AppError { ret := _m.Called(userID, data)