MM-12393 Server side of bot accounts. (#10378)

* bots model, store and api (#9903)

* bots model, store and api

Fixes: MM-13100, MM-13101, MM-13103, MM-13105, MMM-13119

* uncomment tests incorrectly commented, and fix merge issues

* add etags support

* add missing licenses

* remove unused sqlbuilder.go (for now...)

* rejig permissions

* split out READ_BOTS into READ_BOTS and READ_OTHERS_BOTS, the latter
implicitly allowing the former
* make MANAGE_OTHERS_BOTS imply MANAGE_BOTS

* conform to general rest api pattern

* eliminate redundant http.StatusOK

* Update api4/bot.go

Co-Authored-By: lieut-data <jesse.hallam@gmail.com>

* s/model.UserFromBotModel/model.UserFromBot/g

* Update model/bot.go

Co-Authored-By: lieut-data <jesse.hallam@gmail.com>

* Update model/client4.go

Co-Authored-By: lieut-data <jesse.hallam@gmail.com>

* move sessionHasPermissionToManageBot to app/authorization.go

* use api.ApiSessionRequired for createBot

* introduce BOT_DESCRIPTION_MAX_RUNES constant

* MM-13512 Prevent getting a user by email based on privacy settings (#10021)

* MM-13512 Prevent getting a user by email based on privacy settings

* Add additional config settings to tests

* upgrade db to 5.7 (#10019)

* MM-13526 Add validation when setting a user's Locale field (#10022)

* Fix typos (#10024)

* Fixing first user being created with system admin privilages without being explicity specified. (#10014)

* Revert "Support for Embeded chat (#9129)" (#10017)

This reverts commit 3fcecd521a.

* s/DisableBot/UpdateBotActive

* add permissions on upgrade

* Update NOTICE.txt (#10054)

- add new dependency (text)
- handle switch to forked dependency (go-gomail -> go-mail)
- misc copyright owner updates

* avoid leaking bot knowledge without permission

* [GH-6798] added a new api endpoint to get the bulk reactions for posts (#10049)

* 6798 added a new api to get the bulk reactions for posts

* 6798 added the permsission check before getting the reactions

* GH-6798 added a new app function for the new endpoint

* 6798 added a store method to get reactions for multiple posts

* 6798 connected the app function with the new store function

* 6798 fixed the review comments

* MM-13559 Update model.post.is_valid.file_ids.app_error text per report (#10055)

Ticket: https://mattermost.atlassian.net/browse/MM-13559
Report: https://github.com/mattermost/mattermost-server/issues/10023

* Trigger Login Hooks with OAuth (#10061)

* make BotStore.GetAll deterministic even on duplicate CreateAt

* fix spurious TestMuteCommandSpecificChannel test failure

See
https://community-daily.mattermost.com/core/pl/px9p8s3dzbg1pf3ddrm5cr36uw

* fix race in TestExportUserChannels

* TestExportUserChannels: remove SaveMember call, as it is redundant and used to be silently failing anyway

* MM-13117: bot tokens (#10111)

* eliminate redundant Client/AdminClient declarations

* harden TestUpdateChannelScheme to API failures

* eliminate unnecessary config restoration

* minor cleanup

* make TestGenerateMfaSecret config dependency explicit

* TestCreateUserAccessToken for bots

* TestGetUserAccessToken* for bots

* leverage SessionHasPermissionToUserOrBot for user token APIs

* Test(Revoke|Disable|Enable)UserAccessToken

* make EnableUserAccessTokens explicit, so as to not rely on local config.json

* uncomment TestResetPassword, but still skip

* mark assert(Invalid)Token as helper

* fix whitespace issues

* fix mangled comments

* MM-13116: bot plugin api (#10113)

* MM-13117: expose bot API to plugins

This also changes the `CreatorId` column definition to allow for plugin
ids, as the default unless the plugin overrides is to use the plugin id
here. This branch hasn't hit master yet, so no migration needed.

* gofmt issues

* expunge use of BotList in plugin/client API

* introduce model.BotGetOptions

* use botUserId term for clarity

* MM-13129 Adding functionality to deal with orphaned bots (#10238)

* Add way to list orphaned bots.

* Add /assign route to modify ownership of bot accounts.

* Apply suggestions from code review

Co-Authored-By: crspeller <crspeller@gmail.com>

* MM-13120: add IsBot field to returned user objects (#10103)

* MM-13104: forbid bot login (#10251)

* MM-13104: disallow bot login

* fix shadowing

* MM-13136 Disable user bots when user is disabled. (#10293)

* Disable user bots when user is disabled.

* Grammer.

Co-Authored-By: crspeller <crspeller@gmail.com>

* Fixing bot branch for test changes.

* Don't use external dependancies in bot plugin tests.

* Rename bot CreatorId to OwnerId

* Adding ability to re-enable bots

* Fixing IsBot to not attempt to be saved to DB.

* Adding diagnostics and licencing counting for bot accounts.

* Modifying gorp to allow reading of '-' fields.

* Removing unnessisary nil values from UserCountOptions.

* Changing comment to GoDoc format

* Improving user count SQL

* Some improvments from feedback.

* Omit empty on User.IsBot
This commit is contained in:
Christopher Speller
2019-03-05 07:06:45 -08:00
committed by GitHub
parent 80e0d01fe5
commit 06b579d18a
53 changed files with 5951 additions and 403 deletions

6
Gopkg.lock generated
View File

@@ -443,12 +443,12 @@
revision = "60711f1a8329503b04e1c88535f419d0bb440bff"
[[projects]]
branch = "master"
digest = "1:f44dea49cf8d9389516c537b7ef61bfb5836a9bf485e213673917258413f24c3"
branch = "mm-14140"
digest = "1:697ef923111e6b1a9fc09d4e799cb071469dbaccb371eb707832407cbde07413"
name = "github.com/mattermost/gorp"
packages = ["."]
pruneopts = "UT"
revision = "520a119fe3536337cf8feeaf76afae0f5ae193f1"
revision = "a13faa4e058457a4e0e0c02268cd6561b55c9702"
[[projects]]
branch = "master"

View File

@@ -24,6 +24,9 @@ type Routes struct {
UserByUsername *mux.Router // 'api/v4/users/username/{username:[A-Za-z0-9_-\.]+}'
UserByEmail *mux.Router // 'api/v4/users/email/{email}'
Bots *mux.Router // 'api/v4/bots'
Bot *mux.Router // 'api/v4/bots/{bot_user_id:[A-Za-z0-9]+}'
Teams *mux.Router // 'api/v4/teams'
TeamsForUser *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/teams'
Team *mux.Router // 'api/v4/teams/{team_id:[A-Za-z0-9]+}'
@@ -132,6 +135,9 @@ func Init(configservice configservice.ConfigService, globalOptionsFunc app.AppOp
api.BaseRoutes.UserByUsername = api.BaseRoutes.Users.PathPrefix("/username/{username:[A-Za-z0-9\\_\\-\\.]+}").Subrouter()
api.BaseRoutes.UserByEmail = api.BaseRoutes.Users.PathPrefix("/email/{email}").Subrouter()
api.BaseRoutes.Bots = api.BaseRoutes.ApiRoot.PathPrefix("/bots").Subrouter()
api.BaseRoutes.Bot = api.BaseRoutes.ApiRoot.PathPrefix("/bots/{bot_user_id:[A-Za-z0-9]+}").Subrouter()
api.BaseRoutes.Teams = api.BaseRoutes.ApiRoot.PathPrefix("/teams").Subrouter()
api.BaseRoutes.TeamsForUser = api.BaseRoutes.User.PathPrefix("/teams").Subrouter()
api.BaseRoutes.Team = api.BaseRoutes.Teams.PathPrefix("/{team_id:[A-Za-z0-9]+}").Subrouter()
@@ -209,6 +215,7 @@ func Init(configservice configservice.ConfigService, globalOptionsFunc app.AppOp
api.BaseRoutes.Groups = api.BaseRoutes.ApiRoot.PathPrefix("/groups").Subrouter()
api.InitUser()
api.InitBot()
api.InitTeam()
api.InitChannel()
api.InitPost()

200
api4/bot.go Normal file
View File

@@ -0,0 +1,200 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
import (
"net/http"
"github.com/mattermost/mattermost-server/model"
)
func (api *API) InitBot() {
api.BaseRoutes.Bots.Handle("", api.ApiSessionRequired(createBot)).Methods("POST")
api.BaseRoutes.Bot.Handle("", api.ApiSessionRequired(patchBot)).Methods("PUT")
api.BaseRoutes.Bot.Handle("", api.ApiSessionRequired(getBot)).Methods("GET")
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("/assign/{user_id:[A-Za-z0-9]+}", api.ApiSessionRequired(assignBot)).Methods("POST")
}
func createBot(c *Context, w http.ResponseWriter, r *http.Request) {
botPatch := model.BotPatchFromJson(r.Body)
if botPatch == nil {
c.SetInvalidParam("bot")
return
}
bot := &model.Bot{
OwnerId: c.App.Session.UserId,
}
bot.Patch(botPatch)
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_CREATE_BOT) {
c.SetPermissionError(model.PERMISSION_CREATE_BOT)
return
}
createdBot, err := c.App.CreateBot(bot)
if err != nil {
c.Err = err
return
}
w.WriteHeader(http.StatusCreated)
w.Write(createdBot.ToJson())
}
func patchBot(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireBotUserId()
if c.Err != nil {
return
}
botUserId := c.Params.BotUserId
botPatch := model.BotPatchFromJson(r.Body)
if botPatch == nil {
c.SetInvalidParam("bot")
return
}
if err := c.App.SessionHasPermissionToManageBot(c.App.Session, botUserId); err != nil {
c.Err = err
return
}
updatedBot, err := c.App.PatchBot(botUserId, botPatch)
if err != nil {
c.Err = err
return
}
w.Write(updatedBot.ToJson())
}
func getBot(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireBotUserId()
if c.Err != nil {
return
}
botUserId := c.Params.BotUserId
includeDeleted := r.URL.Query().Get("include_deleted") == "true"
bot, err := c.App.GetBot(botUserId, includeDeleted)
if err != nil {
c.Err = err
return
}
if c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_READ_OTHERS_BOTS) {
// Allow access to any bot.
} else if bot.OwnerId == c.App.Session.UserId {
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_READ_BOTS) {
// Pretend like the bot doesn't exist at all to avoid revealing that the
// user is a bot. It's kind of silly in this case, sine we created the bot,
// but we don't have read bot permissions.
c.Err = model.MakeBotNotFoundError(botUserId)
return
}
} else {
// Pretend like the bot doesn't exist at all, to avoid revealing that the
// user is a bot.
c.Err = model.MakeBotNotFoundError(botUserId)
return
}
if c.HandleEtag(bot.Etag(), "Get Bot", w, r) {
return
}
w.Write(bot.ToJson())
}
func getBots(c *Context, w http.ResponseWriter, r *http.Request) {
includeDeleted := r.URL.Query().Get("include_deleted") == "true"
onlyOrphaned := r.URL.Query().Get("only_orphaned") == "true"
var OwnerId string
if c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_READ_OTHERS_BOTS) {
// Get bots created by any user.
OwnerId = ""
} else if c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_READ_BOTS) {
// Only get bots created by this user.
OwnerId = c.App.Session.UserId
} else {
c.SetPermissionError(model.PERMISSION_READ_BOTS)
return
}
bots, err := c.App.GetBots(&model.BotGetOptions{
Page: c.Params.Page,
PerPage: c.Params.PerPage,
OwnerId: OwnerId,
IncludeDeleted: includeDeleted,
OnlyOrphaned: onlyOrphaned,
})
if err != nil {
c.Err = err
return
}
if c.HandleEtag(bots.Etag(), "Get Bots", w, r) {
return
}
w.Write(bots.ToJson())
}
func disableBot(c *Context, w http.ResponseWriter, r *http.Request) {
updateBotActive(c, w, r, false)
}
func enableBot(c *Context, w http.ResponseWriter, r *http.Request) {
updateBotActive(c, w, r, true)
}
func updateBotActive(c *Context, w http.ResponseWriter, r *http.Request, active bool) {
c.RequireBotUserId()
if c.Err != nil {
return
}
botUserId := c.Params.BotUserId
if err := c.App.SessionHasPermissionToManageBot(c.App.Session, botUserId); err != nil {
c.Err = err
return
}
bot, err := c.App.UpdateBotActive(botUserId, active)
if err != nil {
c.Err = err
return
}
w.Write(bot.ToJson())
}
func assignBot(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireUserId()
c.RequireBotUserId()
if c.Err != nil {
return
}
botUserId := c.Params.BotUserId
userId := c.Params.UserId
if err := c.App.SessionHasPermissionToManageBot(c.App.Session, botUserId); err != nil {
c.Err = err
return
}
bot, err := c.App.UpdateBotOwner(botUserId, userId)
if err != nil {
c.Err = err
return
}
w.Write(bot.ToJson())
}

938
api4/bot_test.go Normal file
View File

@@ -0,0 +1,938 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
import (
"io/ioutil"
"strings"
"testing"
"github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/require"
)
func TestCreateBot(t *testing.T) {
t.Run("create bot without permissions", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
_, resp := th.Client.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
DisplayName: "a bot",
Description: "bot",
})
CheckErrorMessage(t, resp, "api.context.permissions.app_error")
})
t.Run("create bot with permissions", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bot := &model.Bot{
Username: GenerateTestUsername(),
DisplayName: "a bot",
Description: "bot",
}
createdBot, resp := th.Client.CreateBot(bot)
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(createdBot.UserId)
require.Equal(t, bot.Username, createdBot.Username)
require.Equal(t, bot.DisplayName, createdBot.DisplayName)
require.Equal(t, bot.Description, createdBot.Description)
require.Equal(t, th.BasicUser.Id, createdBot.OwnerId)
})
t.Run("create invalid bot", func(t *testing.T) {
th := Setup().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)
_, resp := th.Client.CreateBot(&model.Bot{
Username: "username",
DisplayName: "a bot",
Description: strings.Repeat("x", 1025),
})
CheckErrorMessage(t, resp, "model.bot.is_valid.description.app_error")
})
}
func TestPatchBot(t *testing.T) {
t.Run("patch non-existent bot", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
_, resp := th.SystemAdminClient.PatchBot(model.NewId(), &model.BotPatch{})
CheckNotFoundStatus(t, resp)
})
t.Run("patch someone else's bot without permission", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
createdBot, resp := th.SystemAdminClient.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
DisplayName: "a bot",
Description: "bot",
})
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(createdBot.UserId)
_, resp = th.Client.PatchBot(createdBot.UserId, &model.BotPatch{})
CheckErrorMessage(t, resp, "store.sql_bot.get.missing.app_error")
})
t.Run("patch someone else's bot without permission, but with read others permission", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_READ_OTHERS_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
createdBot, resp := th.SystemAdminClient.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
DisplayName: "a bot",
Description: "bot",
})
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(createdBot.UserId)
_, resp = th.Client.PatchBot(createdBot.UserId, &model.BotPatch{})
CheckErrorMessage(t, resp, "api.context.permissions.app_error")
})
t.Run("patch someone else's bot with permission", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_MANAGE_OTHERS_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
createdBot, resp := th.SystemAdminClient.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
DisplayName: "a bot",
Description: "bot",
})
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(createdBot.UserId)
botPatch := &model.BotPatch{
Username: sToP(GenerateTestUsername()),
DisplayName: sToP("an updated bot"),
Description: sToP("updated bot"),
}
patchedBot, resp := th.Client.PatchBot(createdBot.UserId, botPatch)
CheckOKStatus(t, resp)
require.Equal(t, *botPatch.Username, patchedBot.Username)
require.Equal(t, *botPatch.DisplayName, patchedBot.DisplayName)
require.Equal(t, *botPatch.Description, patchedBot.Description)
require.Equal(t, th.SystemAdminUser.Id, patchedBot.OwnerId)
})
t.Run("patch my bot without permission", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
createdBot, resp := th.Client.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
DisplayName: "a bot",
Description: "bot",
})
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(createdBot.UserId)
botPatch := &model.BotPatch{
Username: sToP(GenerateTestUsername()),
DisplayName: sToP("an updated bot"),
Description: sToP("updated bot"),
}
_, resp = th.Client.PatchBot(createdBot.UserId, botPatch)
CheckErrorMessage(t, resp, "store.sql_bot.get.missing.app_error")
})
t.Run("patch my bot without permission, but with read permission", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
createdBot, resp := th.Client.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
DisplayName: "a bot",
Description: "bot",
})
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(createdBot.UserId)
botPatch := &model.BotPatch{
Username: sToP(GenerateTestUsername()),
DisplayName: sToP("an updated bot"),
Description: sToP("updated bot"),
}
_, resp = th.Client.PatchBot(createdBot.UserId, botPatch)
CheckErrorMessage(t, resp, "api.context.permissions.app_error")
})
t.Run("patch my bot with permission", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_MANAGE_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
createdBot, resp := th.Client.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
DisplayName: "a bot",
Description: "bot",
})
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(createdBot.UserId)
botPatch := &model.BotPatch{
Username: sToP(GenerateTestUsername()),
DisplayName: sToP("an updated bot"),
Description: sToP("updated bot"),
}
patchedBot, resp := th.Client.PatchBot(createdBot.UserId, botPatch)
CheckOKStatus(t, resp)
require.Equal(t, *botPatch.Username, patchedBot.Username)
require.Equal(t, *botPatch.DisplayName, patchedBot.DisplayName)
require.Equal(t, *botPatch.Description, patchedBot.Description)
require.Equal(t, th.BasicUser.Id, patchedBot.OwnerId)
})
t.Run("partial patch my bot with permission", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_MANAGE_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bot := &model.Bot{
Username: GenerateTestUsername(),
DisplayName: "a bot",
Description: "bot",
}
createdBot, resp := th.Client.CreateBot(bot)
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(createdBot.UserId)
botPatch := &model.BotPatch{
Username: sToP(GenerateTestUsername()),
}
patchedBot, resp := th.Client.PatchBot(createdBot.UserId, botPatch)
CheckOKStatus(t, resp)
require.Equal(t, *botPatch.Username, patchedBot.Username)
require.Equal(t, bot.DisplayName, patchedBot.DisplayName)
require.Equal(t, bot.Description, patchedBot.Description)
require.Equal(t, th.BasicUser.Id, patchedBot.OwnerId)
})
t.Run("update bot, internally managed fields ignored", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_MANAGE_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
createdBot, resp := th.Client.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
DisplayName: "a bot",
Description: "bot",
})
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(createdBot.UserId)
r, err := th.Client.DoApiPut(th.Client.GetBotRoute(createdBot.UserId), `{"creator_id":"`+th.BasicUser2.Id+`"}`)
require.Nil(t, err)
defer func() {
_, _ = ioutil.ReadAll(r.Body)
_ = r.Body.Close()
}()
patchedBot := model.BotFromJson(r.Body)
resp = model.BuildResponse(r)
CheckOKStatus(t, resp)
require.Equal(t, th.BasicUser.Id, patchedBot.OwnerId)
})
}
func TestGetBot(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
bot1, resp := th.SystemAdminClient.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
DisplayName: "a bot",
Description: "the first bot",
})
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(bot1.UserId)
bot2, resp := th.SystemAdminClient.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
DisplayName: "another bot",
Description: "the second bot",
})
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(bot2.UserId)
deletedBot, resp := th.SystemAdminClient.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
Description: "a deleted bot",
})
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(deletedBot.UserId)
deletedBot, resp = th.SystemAdminClient.DisableBot(deletedBot.UserId)
CheckOKStatus(t, resp)
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
myBot, resp := th.Client.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
DisplayName: "my bot",
Description: "a bot created by non-admin",
})
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(myBot.UserId)
th.RemovePermissionFromRole(model.PERMISSION_CREATE_BOT.Id, model.TEAM_USER_ROLE_ID)
t.Run("get unknown bot", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_OTHERS_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
_, resp := th.Client.GetBot(model.NewId(), "")
CheckNotFoundStatus(t, resp)
})
t.Run("get bot1", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_OTHERS_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bot, resp := th.Client.GetBot(bot1.UserId, "")
CheckOKStatus(t, resp)
require.Equal(t, bot1, bot)
bot, resp = th.Client.GetBot(bot1.UserId, bot.Etag())
CheckEtag(t, bot, resp)
})
t.Run("get bot2", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_OTHERS_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bot, resp := th.Client.GetBot(bot2.UserId, "")
CheckOKStatus(t, resp)
require.Equal(t, bot2, bot)
bot, resp = th.Client.GetBot(bot2.UserId, bot.Etag())
CheckEtag(t, bot, resp)
})
t.Run("get bot1 without READ_OTHERS_BOTS permission", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_MANAGE_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_MANAGE_OTHERS_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
_, resp := th.Client.GetBot(bot1.UserId, "")
CheckErrorMessage(t, resp, "store.sql_bot.get.missing.app_error")
})
t.Run("get myBot without READ_BOTS OR READ_OTHERS_BOTS permissions", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_MANAGE_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_MANAGE_OTHERS_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
_, resp := th.Client.GetBot(myBot.UserId, "")
CheckErrorMessage(t, resp, "store.sql_bot.get.missing.app_error")
})
t.Run("get deleted bot", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_OTHERS_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
_, resp := th.Client.GetBot(deletedBot.UserId, "")
CheckNotFoundStatus(t, resp)
})
t.Run("get deleted bot, include deleted", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_OTHERS_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bot, resp := th.Client.GetBotIncludeDeleted(deletedBot.UserId, "")
CheckOKStatus(t, resp)
require.NotEqual(t, 0, bot.DeleteAt)
deletedBot.UpdateAt = bot.UpdateAt
deletedBot.DeleteAt = bot.DeleteAt
require.Equal(t, deletedBot, bot)
bot, resp = th.Client.GetBotIncludeDeleted(deletedBot.UserId, bot.Etag())
CheckEtag(t, bot, resp)
})
}
func TestGetBots(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
bot1, resp := th.SystemAdminClient.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
DisplayName: "a bot",
Description: "the first bot",
})
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(bot1.UserId)
deletedBot1, resp := th.SystemAdminClient.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
Description: "a deleted bot",
})
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(deletedBot1.UserId)
deletedBot1, resp = th.SystemAdminClient.DisableBot(deletedBot1.UserId)
CheckOKStatus(t, resp)
bot2, resp := th.SystemAdminClient.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
DisplayName: "another bot",
Description: "the second bot",
})
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(bot2.UserId)
bot3, resp := th.SystemAdminClient.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
DisplayName: "another bot",
Description: "the third bot",
})
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(bot3.UserId)
deletedBot2, resp := th.SystemAdminClient.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
Description: "a deleted bot",
})
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(deletedBot2.UserId)
deletedBot2, resp = th.SystemAdminClient.DisableBot(deletedBot2.UserId)
CheckOKStatus(t, resp)
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser2.Id, model.TEAM_USER_ROLE_ID, false)
th.LoginBasic2()
orphanedBot, resp := th.Client.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
Description: "an oprphaned bot",
})
CheckCreatedStatus(t, resp)
th.LoginBasic()
defer th.App.PermanentDeleteBot(orphanedBot.UserId)
// Automatic deactivation disabled
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.DisableBotsWhenOwnerIsDeactivated = false
})
_, resp = th.SystemAdminClient.DeleteUser(th.BasicUser2.Id)
CheckOKStatus(t, resp)
t.Run("get bots, page=0, perPage=10", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_OTHERS_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bots, resp := th.Client.GetBots(0, 10, "")
CheckOKStatus(t, resp)
require.Equal(t, []*model.Bot{bot1, bot2, bot3, orphanedBot}, bots)
botList := model.BotList(bots)
bots, resp = th.Client.GetBots(0, 10, botList.Etag())
CheckEtag(t, bots, resp)
})
t.Run("get bots, page=0, perPage=1", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_OTHERS_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bots, resp := th.Client.GetBots(0, 1, "")
CheckOKStatus(t, resp)
require.Equal(t, []*model.Bot{bot1}, bots)
botList := model.BotList(bots)
bots, resp = th.Client.GetBots(0, 1, botList.Etag())
CheckEtag(t, bots, resp)
})
t.Run("get bots, page=1, perPage=2", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_OTHERS_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bots, resp := th.Client.GetBots(1, 2, "")
CheckOKStatus(t, resp)
require.Equal(t, []*model.Bot{bot3, orphanedBot}, bots)
botList := model.BotList(bots)
bots, resp = th.Client.GetBots(1, 2, botList.Etag())
CheckEtag(t, bots, resp)
})
t.Run("get bots, page=2, perPage=2", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_OTHERS_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bots, resp := th.Client.GetBots(2, 2, "")
CheckOKStatus(t, resp)
require.Equal(t, []*model.Bot{}, bots)
botList := model.BotList(bots)
bots, resp = th.Client.GetBots(2, 2, botList.Etag())
CheckEtag(t, bots, resp)
})
t.Run("get bots, page=0, perPage=10, include deleted", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_OTHERS_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bots, resp := th.Client.GetBotsIncludeDeleted(0, 10, "")
CheckOKStatus(t, resp)
require.Equal(t, []*model.Bot{bot1, deletedBot1, bot2, bot3, deletedBot2, orphanedBot}, bots)
botList := model.BotList(bots)
bots, resp = th.Client.GetBotsIncludeDeleted(0, 10, botList.Etag())
CheckEtag(t, bots, resp)
})
t.Run("get bots, page=0, perPage=1, include deleted", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_OTHERS_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bots, resp := th.Client.GetBotsIncludeDeleted(0, 1, "")
CheckOKStatus(t, resp)
require.Equal(t, []*model.Bot{bot1}, bots)
botList := model.BotList(bots)
bots, resp = th.Client.GetBotsIncludeDeleted(0, 1, botList.Etag())
CheckEtag(t, bots, resp)
})
t.Run("get bots, page=1, perPage=2, include deleted", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_OTHERS_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bots, resp := th.Client.GetBotsIncludeDeleted(1, 2, "")
CheckOKStatus(t, resp)
require.Equal(t, []*model.Bot{bot2, bot3}, bots)
botList := model.BotList(bots)
bots, resp = th.Client.GetBotsIncludeDeleted(1, 2, botList.Etag())
CheckEtag(t, bots, resp)
})
t.Run("get bots, page=2, perPage=2, include deleted", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_OTHERS_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bots, resp := th.Client.GetBotsIncludeDeleted(2, 2, "")
CheckOKStatus(t, resp)
require.Equal(t, []*model.Bot{deletedBot2, orphanedBot}, bots)
botList := model.BotList(bots)
bots, resp = th.Client.GetBotsIncludeDeleted(2, 2, botList.Etag())
CheckEtag(t, bots, resp)
})
t.Run("get bots, page=0, perPage=10, only orphaned", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_OTHERS_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bots, resp := th.Client.GetBotsOrphaned(0, 10, "")
CheckOKStatus(t, resp)
require.Equal(t, []*model.Bot{orphanedBot}, bots)
botList := model.BotList(bots)
bots, resp = th.Client.GetBotsOrphaned(0, 10, botList.Etag())
CheckEtag(t, bots, resp)
})
t.Run("get bots without permission", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_MANAGE_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_MANAGE_OTHERS_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
_, resp := th.Client.GetBots(0, 10, "")
CheckErrorMessage(t, resp, "api.context.permissions.app_error")
})
}
func TestDisableBot(t *testing.T) {
t.Run("disable non-existent bot", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
_, resp := th.Client.DisableBot(model.NewId())
CheckNotFoundStatus(t, resp)
})
t.Run("disable bot without permission", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bot := &model.Bot{
Username: GenerateTestUsername(),
Description: "bot",
}
createdBot, resp := th.Client.CreateBot(bot)
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(createdBot.UserId)
_, resp = th.Client.DisableBot(createdBot.UserId)
CheckErrorMessage(t, resp, "store.sql_bot.get.missing.app_error")
})
t.Run("disable bot without permission, but with read permission", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bot := &model.Bot{
Username: GenerateTestUsername(),
Description: "bot",
}
createdBot, resp := th.Client.CreateBot(bot)
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(createdBot.UserId)
_, resp = th.Client.DisableBot(createdBot.UserId)
CheckErrorMessage(t, resp, "api.context.permissions.app_error")
})
t.Run("disable bot with permission", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_MANAGE_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bot, resp := th.Client.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
Description: "bot",
})
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(bot.UserId)
enabledBot1, resp := th.Client.DisableBot(bot.UserId)
CheckOKStatus(t, resp)
bot.UpdateAt = enabledBot1.UpdateAt
bot.DeleteAt = enabledBot1.DeleteAt
require.Equal(t, bot, enabledBot1)
// Check bot disabled
disab, resp := th.SystemAdminClient.GetBotIncludeDeleted(bot.UserId, "")
CheckOKStatus(t, resp)
require.NotZero(t, disab.DeleteAt)
// Disabling should be idempotent.
enabledBot2, resp := th.Client.DisableBot(bot.UserId)
CheckOKStatus(t, resp)
require.Equal(t, bot, enabledBot2)
})
}
func TestEnableBot(t *testing.T) {
t.Run("enable non-existent bot", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
_, resp := th.Client.EnableBot(model.NewId())
CheckNotFoundStatus(t, resp)
})
t.Run("enable bot without permission", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bot := &model.Bot{
Username: GenerateTestUsername(),
Description: "bot",
}
createdBot, resp := th.Client.CreateBot(bot)
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(createdBot.UserId)
_, resp = th.SystemAdminClient.DisableBot(createdBot.UserId)
CheckOKStatus(t, resp)
_, resp = th.Client.EnableBot(createdBot.UserId)
CheckErrorMessage(t, resp, "store.sql_bot.get.missing.app_error")
})
t.Run("enable bot without permission, but with read permission", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bot := &model.Bot{
Username: GenerateTestUsername(),
Description: "bot",
}
createdBot, resp := th.Client.CreateBot(bot)
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(createdBot.UserId)
_, resp = th.SystemAdminClient.DisableBot(createdBot.UserId)
CheckOKStatus(t, resp)
_, resp = th.Client.EnableBot(createdBot.UserId)
CheckErrorMessage(t, resp, "api.context.permissions.app_error")
})
t.Run("enable bot with permission", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.TEAM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_MANAGE_BOTS.Id, model.TEAM_USER_ROLE_ID)
th.App.UpdateUserRoles(th.BasicUser.Id, model.TEAM_USER_ROLE_ID, false)
bot, resp := th.Client.CreateBot(&model.Bot{
Username: GenerateTestUsername(),
Description: "bot",
})
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(bot.UserId)
_, resp = th.SystemAdminClient.DisableBot(bot.UserId)
CheckOKStatus(t, resp)
enabledBot1, resp := th.Client.EnableBot(bot.UserId)
CheckOKStatus(t, resp)
bot.UpdateAt = enabledBot1.UpdateAt
bot.DeleteAt = enabledBot1.DeleteAt
require.Equal(t, bot, enabledBot1)
// Check bot enabled
enab, resp := th.SystemAdminClient.GetBotIncludeDeleted(bot.UserId, "")
CheckOKStatus(t, resp)
require.Zero(t, enab.DeleteAt)
// Disabling should be idempotent.
enabledBot2, resp := th.Client.EnableBot(bot.UserId)
CheckOKStatus(t, resp)
require.Equal(t, bot, enabledBot2)
})
}
func TestAssignBot(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
t.Run("claim non-existent bot", func(t *testing.T) {
_, resp := th.SystemAdminClient.AssignBot(model.NewId(), model.NewId())
CheckNotFoundStatus(t, resp)
})
t.Run("system admin assign bot", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.SYSTEM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.SYSTEM_USER_ROLE_ID)
bot := &model.Bot{
Username: GenerateTestUsername(),
Description: "bot",
}
bot, resp := th.Client.CreateBot(bot)
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(bot.UserId)
before, resp := th.Client.GetBot(bot.UserId, "")
CheckOKStatus(t, resp)
require.Equal(t, th.BasicUser.Id, before.OwnerId)
_, resp = th.SystemAdminClient.AssignBot(bot.UserId, th.SystemAdminUser.Id)
CheckOKStatus(t, resp)
// Original owner doesn't have read others bots permission, therefore can't see bot anymore
_, resp = th.Client.GetBot(bot.UserId, "")
CheckNotFoundStatus(t, resp)
// System admin can see creator ID has changed
after, resp := th.SystemAdminClient.GetBot(bot.UserId, "")
CheckOKStatus(t, resp)
require.Equal(t, th.SystemAdminUser.Id, after.OwnerId)
// Assign back to user without permissions to manage
_, resp = th.SystemAdminClient.AssignBot(bot.UserId, th.BasicUser.Id)
CheckOKStatus(t, resp)
after, resp = th.SystemAdminClient.GetBot(bot.UserId, "")
CheckOKStatus(t, resp)
require.Equal(t, th.BasicUser.Id, after.OwnerId)
})
t.Run("random user assign bot", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.SYSTEM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.SYSTEM_USER_ROLE_ID)
bot := &model.Bot{
Username: GenerateTestUsername(),
Description: "bot",
}
createdBot, resp := th.Client.CreateBot(bot)
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(createdBot.UserId)
th.LoginBasic2()
// Without permission to read others bots it doesn't exist
_, resp = th.Client.AssignBot(createdBot.UserId, th.BasicUser2.Id)
CheckErrorMessage(t, resp, "store.sql_bot.get.missing.app_error")
// With permissions to read we don't have permissions to modify
th.AddPermissionToRole(model.PERMISSION_READ_OTHERS_BOTS.Id, model.SYSTEM_USER_ROLE_ID)
_, resp = th.Client.AssignBot(createdBot.UserId, th.BasicUser2.Id)
CheckErrorMessage(t, resp, "api.context.permissions.app_error")
th.LoginBasic()
})
t.Run("delegated user assign bot", func(t *testing.T) {
defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.SYSTEM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.SYSTEM_USER_ROLE_ID)
bot := &model.Bot{
Username: GenerateTestUsername(),
Description: "bot",
}
bot, resp := th.Client.CreateBot(bot)
CheckCreatedStatus(t, resp)
defer th.App.PermanentDeleteBot(bot.UserId)
// Simulate custom role by just changing the system user role
th.AddPermissionToRole(model.PERMISSION_CREATE_BOT.Id, model.SYSTEM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_BOTS.Id, model.SYSTEM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_READ_OTHERS_BOTS.Id, model.SYSTEM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_MANAGE_BOTS.Id, model.SYSTEM_USER_ROLE_ID)
th.AddPermissionToRole(model.PERMISSION_MANAGE_OTHERS_BOTS.Id, model.SYSTEM_USER_ROLE_ID)
th.LoginBasic2()
_, resp = th.Client.AssignBot(bot.UserId, th.BasicUser2.Id)
CheckOKStatus(t, resp)
after, resp := th.SystemAdminClient.GetBot(bot.UserId, "")
CheckOKStatus(t, resp)
require.Equal(t, th.BasicUser2.Id, after.OwnerId)
})
}
func sToP(s string) *string {
return &s
}

View File

@@ -1446,7 +1446,7 @@ func createUserAccessToken(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if !c.App.SessionHasPermissionToUser(c.App.Session, c.Params.UserId) {
if !c.App.SessionHasPermissionToUserOrBot(c.App.Session, c.Params.UserId) {
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
return
}
@@ -1515,7 +1515,7 @@ func getUserAccessTokensForUser(c *Context, w http.ResponseWriter, r *http.Reque
return
}
if !c.App.SessionHasPermissionToUser(c.App.Session, c.Params.UserId) {
if !c.App.SessionHasPermissionToUserOrBot(c.App.Session, c.Params.UserId) {
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
return
}
@@ -1546,7 +1546,7 @@ func getUserAccessToken(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if !c.App.SessionHasPermissionToUser(c.App.Session, accessToken.UserId) {
if !c.App.SessionHasPermissionToUserOrBot(c.App.Session, accessToken.UserId) {
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
return
}
@@ -1575,7 +1575,7 @@ func revokeUserAccessToken(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if !c.App.SessionHasPermissionToUser(c.App.Session, accessToken.UserId) {
if !c.App.SessionHasPermissionToUserOrBot(c.App.Session, accessToken.UserId) {
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
return
}
@@ -1611,7 +1611,7 @@ func disableUserAccessToken(c *Context, w http.ResponseWriter, r *http.Request)
return
}
if !c.App.SessionHasPermissionToUser(c.App.Session, accessToken.UserId) {
if !c.App.SessionHasPermissionToUserOrBot(c.App.Session, accessToken.UserId) {
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
return
}
@@ -1647,7 +1647,7 @@ func enableUserAccessToken(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if !c.App.SessionHasPermissionToUser(c.App.Session, accessToken.UserId) {
if !c.App.SessionHasPermissionToUserOrBot(c.App.Session, accessToken.UserId) {
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
return
}

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,7 @@ const (
func (a *App) GetAnalytics(name string, teamId string) (model.AnalyticsRows, *model.AppError) {
skipIntensiveQueries := false
var systemUserCount int64
r := <-a.Srv.Store.User().AnalyticsUniqueUserCount("")
r := <-a.Srv.Store.User().Count(model.UserCountOptions{})
if r.Err != nil {
return nil, r.Err
}
@@ -53,7 +53,9 @@ func (a *App) GetAnalytics(name string, teamId string) (model.AnalyticsRows, *mo
if teamId == "" {
userInactiveChan = a.Srv.Store.User().AnalyticsGetInactiveUsersCount()
} else {
userChan = a.Srv.Store.User().AnalyticsUniqueUserCount(teamId)
userChan = a.Srv.Store.User().Count(model.UserCountOptions{
TeamId: teamId,
})
}
var postChan store.StoreChannel

View File

@@ -163,6 +163,11 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) {
model.PERMISSION_CREATE_USER_ACCESS_TOKEN.Id,
model.PERMISSION_READ_USER_ACCESS_TOKEN.Id,
model.PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id,
model.PERMISSION_CREATE_BOT.Id,
model.PERMISSION_READ_BOTS.Id,
model.PERMISSION_READ_OTHERS_BOTS.Id,
model.PERMISSION_MANAGE_BOTS.Id,
model.PERMISSION_MANAGE_OTHERS_BOTS.Id,
model.PERMISSION_REMOVE_OTHERS_REACTIONS.Id,
model.PERMISSION_LIST_TEAM_CHANNELS.Id,
model.PERMISSION_JOIN_PUBLIC_CHANNELS.Id,
@@ -331,6 +336,11 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) {
model.PERMISSION_CREATE_USER_ACCESS_TOKEN.Id,
model.PERMISSION_READ_USER_ACCESS_TOKEN.Id,
model.PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id,
model.PERMISSION_CREATE_BOT.Id,
model.PERMISSION_READ_BOTS.Id,
model.PERMISSION_READ_OTHERS_BOTS.Id,
model.PERMISSION_MANAGE_BOTS.Id,
model.PERMISSION_MANAGE_OTHERS_BOTS.Id,
model.PERMISSION_REMOVE_OTHERS_REACTIONS.Id,
model.PERMISSION_LIST_TEAM_CHANNELS.Id,
model.PERMISSION_JOIN_PUBLIC_CHANNELS.Id,
@@ -465,6 +475,11 @@ func TestDoEmojisPermissionsMigration(t *testing.T) {
model.PERMISSION_CREATE_USER_ACCESS_TOKEN.Id,
model.PERMISSION_READ_USER_ACCESS_TOKEN.Id,
model.PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id,
model.PERMISSION_CREATE_BOT.Id,
model.PERMISSION_READ_BOTS.Id,
model.PERMISSION_READ_OTHERS_BOTS.Id,
model.PERMISSION_MANAGE_BOTS.Id,
model.PERMISSION_MANAGE_OTHERS_BOTS.Id,
model.PERMISSION_REMOVE_OTHERS_REACTIONS.Id,
model.PERMISSION_LIST_TEAM_CHANNELS.Id,
model.PERMISSION_JOIN_PUBLIC_CHANNELS.Id,

View File

@@ -143,6 +143,10 @@ func (a *App) CheckUserPreflightAuthenticationCriteria(user *model.User, mfaToke
return err
}
if err := checkUserNotBot(user); err != nil {
return err
}
if err := checkUserLoginAttempts(user, *a.Config().ServiceSettings.MaximumLoginAttempts); err != nil {
return err
}
@@ -191,6 +195,13 @@ func checkUserNotDisabled(user *model.User) *model.AppError {
return nil
}
func checkUserNotBot(user *model.User) *model.AppError {
if user.IsBot {
return model.NewAppError("Login", "api.user.login.bot_login_forbidden.app_error", nil, "user_id="+user.Id, http.StatusUnauthorized)
}
return nil
}
func (a *App) authenticateUser(user *model.User, password, mfaToken string) (*model.User, *model.AppError) {
license := a.License()
ldapAvailable := *a.Config().LdapSettings.Enable && a.Ldap != nil && license != nil && *license.Features.LDAP

View File

@@ -12,6 +12,10 @@ import (
"github.com/mattermost/mattermost-server/model"
)
func (a *App) MakePermissionError(permission *model.Permission) *model.AppError {
return model.NewAppError("Permissions", "api.context.permissions.app_error", nil, "userId="+a.Session.UserId+", "+"permission="+permission.Id, http.StatusForbidden)
}
func (a *App) SessionHasPermissionTo(session model.Session, permission *model.Permission) bool {
return a.RolesGrantPermission(session.GetUserRoles(), permission.Id)
}
@@ -98,6 +102,18 @@ func (a *App) SessionHasPermissionToUser(session model.Session, userId string) b
return false
}
func (a *App) SessionHasPermissionToUserOrBot(session model.Session, userId string) bool {
if a.SessionHasPermissionToUser(session, userId) {
return true
}
if err := a.SessionHasPermissionToManageBot(session, userId); err == nil {
return true
}
return false
}
func (a *App) HasPermissionTo(askingUserId string, permission *model.Permission) bool {
user, err := a.GetUser(askingUserId)
if err != nil {
@@ -205,3 +221,35 @@ func (a *App) RolesGrantPermission(roleNames []string, permissionId string) bool
return false
}
// SessionHasPermissionToManageBot returns nil if the session has access to manage the given bot.
// This function deviates from other authorization checks in returning an error instead of just
// a boolean, allowing the permission failure to be exposed with more granularity.
func (a *App) SessionHasPermissionToManageBot(session model.Session, botUserId string) *model.AppError {
existingBot, err := a.GetBot(botUserId, true)
if err != nil {
return err
}
if existingBot.OwnerId == session.UserId {
if !a.SessionHasPermissionTo(session, model.PERMISSION_MANAGE_BOTS) {
if !a.SessionHasPermissionTo(session, model.PERMISSION_READ_BOTS) {
// If the user doesn't have permission to read bots, pretend as if
// the bot doesn't exist at all.
return model.MakeBotNotFoundError(botUserId)
}
return a.MakePermissionError(model.PERMISSION_MANAGE_BOTS)
}
} else {
if !a.SessionHasPermissionTo(session, model.PERMISSION_MANAGE_OTHERS_BOTS) {
if !a.SessionHasPermissionTo(session, model.PERMISSION_READ_OTHERS_BOTS) {
// If the user doesn't have permission to read others' bots,
// pretend as if the bot doesn't exist at all.
return model.MakeBotNotFoundError(botUserId)
}
return a.MakePermissionError(model.PERMISSION_MANAGE_OTHERS_BOTS)
}
}
return nil
}

180
app/bot.go Normal file
View File

@@ -0,0 +1,180 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model"
)
// CreateBot creates the given bot and corresponding user.
func (a *App) CreateBot(bot *model.Bot) (*model.Bot, *model.AppError) {
result := <-a.Srv.Store.User().Save(model.UserFromBot(bot))
if result.Err != nil {
return nil, result.Err
}
bot.UserId = result.Data.(*model.User).Id
result = <-a.Srv.Store.Bot().Save(bot)
if result.Err != nil {
<-a.Srv.Store.User().PermanentDelete(bot.UserId)
return nil, result.Err
}
return result.Data.(*model.Bot), nil
}
// PatchBot applies the given patch to the bot and corresponding user.
func (a *App) PatchBot(botUserId string, botPatch *model.BotPatch) (*model.Bot, *model.AppError) {
bot, err := a.GetBot(botUserId, true)
if err != nil {
return nil, err
}
bot.Patch(botPatch)
result := <-a.Srv.Store.User().Get(botUserId)
if result.Err != nil {
return nil, result.Err
}
user := result.Data.(*model.User)
patchedUser := model.UserFromBot(bot)
user.Id = patchedUser.Id
user.Username = patchedUser.Username
user.Email = patchedUser.Email
user.FirstName = patchedUser.FirstName
if result = <-a.Srv.Store.User().Update(user, true); result.Err != nil {
return nil, result.Err
}
result = <-a.Srv.Store.Bot().Update(bot)
if result.Err != nil {
return nil, result.Err
}
return result.Data.(*model.Bot), nil
}
// GetBot returns the given bot.
func (a *App) GetBot(botUserId string, includeDeleted bool) (*model.Bot, *model.AppError) {
result := <-a.Srv.Store.Bot().Get(botUserId, includeDeleted)
if result.Err != nil {
return nil, result.Err
}
return result.Data.(*model.Bot), nil
}
// GetBots returns the requested page of bots.
func (a *App) GetBots(options *model.BotGetOptions) (model.BotList, *model.AppError) {
result := <-a.Srv.Store.Bot().GetAll(options)
if result.Err != nil {
return nil, result.Err
}
return result.Data.([]*model.Bot), nil
}
// UpdateBotActive marks a bot as active or inactive, along with its corresponding user.
func (a *App) UpdateBotActive(botUserId string, active bool) (*model.Bot, *model.AppError) {
result := <-a.Srv.Store.User().Get(botUserId)
if result.Err != nil {
return nil, result.Err
}
user := result.Data.(*model.User)
if _, err := a.UpdateActive(user, active); err != nil {
return nil, err
}
result = <-a.Srv.Store.Bot().Get(botUserId, true)
if result.Err != nil {
return nil, result.Err
}
bot := result.Data.(*model.Bot)
changed := true
if active && bot.DeleteAt != 0 {
bot.DeleteAt = 0
} else if !active && bot.DeleteAt == 0 {
bot.DeleteAt = model.GetMillis()
} else {
changed = false
}
if changed {
result := <-a.Srv.Store.Bot().Update(bot)
if result.Err != nil {
return nil, result.Err
}
bot = result.Data.(*model.Bot)
}
return bot, nil
}
// PermanentDeleteBot permanently deletes a bot and its corresponding user.
func (a *App) PermanentDeleteBot(botUserId string) *model.AppError {
if result := <-a.Srv.Store.Bot().PermanentDelete(botUserId); result.Err != nil {
return result.Err
}
if result := <-a.Srv.Store.User().PermanentDelete(botUserId); result.Err != nil {
return result.Err
}
return nil
}
// UpdateBotOwner changes a bot's owner to the given value
func (a *App) UpdateBotOwner(botUserId, newOwnerId string) (*model.Bot, *model.AppError) {
result := <-a.Srv.Store.Bot().Get(botUserId, true)
if result.Err != nil {
return nil, result.Err
}
bot := result.Data.(*model.Bot)
bot.OwnerId = newOwnerId
if result = <-a.Srv.Store.Bot().Update(bot); result.Err != nil {
return nil, result.Err
}
return result.Data.(*model.Bot), nil
}
// disableUserBots disables all bots owned by the given user
func (a *App) disableUserBots(userId string) *model.AppError {
perPage := 20
for {
options := &model.BotGetOptions{
OwnerId: userId,
IncludeDeleted: false,
OnlyOrphaned: false,
Page: 0,
PerPage: perPage,
}
userBots, err := a.GetBots(options)
if err != nil {
return err
}
for _, bot := range userBots {
_, err := a.UpdateBotActive(bot.UserId, false)
if err != nil {
mlog.Error("Unable to deactivate bot.", mlog.String("bot_user_id", bot.UserId), mlog.Err(err))
}
}
// Get next set of bots if we got the max number of bots
if len(userBots) == perPage {
options.Page += 1
continue
}
break
}
return nil
}

549
app/bot_test.go Normal file
View File

@@ -0,0 +1,549 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/model"
)
func TestCreateBot(t *testing.T) {
t.Run("invalid bot", func(t *testing.T) {
t.Run("relative to user", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
_, err := th.App.CreateBot(&model.Bot{
Username: "invalid username",
Description: "a bot",
OwnerId: th.BasicUser.Id,
})
require.NotNil(t, err)
require.Equal(t, "model.user.is_valid.username.app_error", err.Id)
})
t.Run("relative to bot", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
_, err := th.App.CreateBot(&model.Bot{
Username: "username",
Description: strings.Repeat("x", 1025),
OwnerId: th.BasicUser.Id,
})
require.NotNil(t, err)
require.Equal(t, "model.bot.is_valid.description.app_error", err.Id)
})
})
t.Run("create bot", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
bot, err := th.App.CreateBot(&model.Bot{
Username: "username",
Description: "a bot",
OwnerId: th.BasicUser.Id,
})
require.Nil(t, err)
defer th.App.PermanentDeleteBot(bot.UserId)
assert.Equal(t, "username", bot.Username)
assert.Equal(t, "a bot", bot.Description)
assert.Equal(t, th.BasicUser.Id, bot.OwnerId)
})
t.Run("create bot, username already used by a non-bot user", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
_, err := th.App.CreateBot(&model.Bot{
Username: th.BasicUser.Username,
Description: "a bot",
OwnerId: th.BasicUser.Id,
})
require.NotNil(t, err)
require.Equal(t, "store.sql_user.save.username_exists.app_error", err.Id)
})
}
func TestPatchBot(t *testing.T) {
t.Run("invalid patch for user", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
bot, err := th.App.CreateBot(&model.Bot{
Username: "username",
Description: "a bot",
OwnerId: th.BasicUser.Id,
})
require.Nil(t, err)
defer th.App.PermanentDeleteBot(bot.UserId)
botPatch := &model.BotPatch{
Username: sToP("invalid username"),
DisplayName: sToP("an updated bot"),
Description: sToP("updated bot"),
}
_, err = th.App.PatchBot(bot.UserId, botPatch)
require.NotNil(t, err)
require.Equal(t, "model.user.is_valid.username.app_error", err.Id)
})
t.Run("invalid patch for bot", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
bot, err := th.App.CreateBot(&model.Bot{
Username: "username",
Description: "a bot",
OwnerId: th.BasicUser.Id,
})
require.Nil(t, err)
defer th.App.PermanentDeleteBot(bot.UserId)
botPatch := &model.BotPatch{
Username: sToP("username"),
DisplayName: sToP("display name"),
Description: sToP(strings.Repeat("x", 1025)),
}
_, err = th.App.PatchBot(bot.UserId, botPatch)
require.NotNil(t, err)
require.Equal(t, "model.bot.is_valid.description.app_error", err.Id)
})
t.Run("patch bot", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
bot := &model.Bot{
Username: "username",
DisplayName: "bot",
Description: "a bot",
OwnerId: th.BasicUser.Id,
}
createdBot, err := th.App.CreateBot(bot)
require.Nil(t, err)
defer th.App.PermanentDeleteBot(createdBot.UserId)
botPatch := &model.BotPatch{
Username: sToP("username2"),
DisplayName: sToP("updated bot"),
Description: sToP("an updated bot"),
}
patchedBot, err := th.App.PatchBot(createdBot.UserId, botPatch)
require.Nil(t, err)
createdBot.Username = "username2"
createdBot.DisplayName = "updated bot"
createdBot.Description = "an updated bot"
createdBot.UpdateAt = patchedBot.UpdateAt
require.Equal(t, createdBot, patchedBot)
})
t.Run("patch bot, username already used by a non-bot user", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
bot, err := th.App.CreateBot(&model.Bot{
Username: "username",
DisplayName: "bot",
Description: "a bot",
OwnerId: th.BasicUser.Id,
})
require.Nil(t, err)
defer th.App.PermanentDeleteBot(bot.UserId)
botPatch := &model.BotPatch{
Username: sToP(th.BasicUser2.Username),
}
_, err = th.App.PatchBot(bot.UserId, botPatch)
require.NotNil(t, err)
require.Equal(t, "store.sql_user.update.username_taken.app_error", err.Id)
})
}
func TestGetBot(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
bot1, err := th.App.CreateBot(&model.Bot{
Username: "username",
Description: "a bot",
OwnerId: th.BasicUser.Id,
})
require.Nil(t, err)
defer th.App.PermanentDeleteBot(bot1.UserId)
bot2, err := th.App.CreateBot(&model.Bot{
Username: "username2",
Description: "a second bot",
OwnerId: th.BasicUser.Id,
})
require.Nil(t, err)
defer th.App.PermanentDeleteBot(bot2.UserId)
deletedBot, err := th.App.CreateBot(&model.Bot{
Username: "username3",
Description: "a deleted bot",
OwnerId: th.BasicUser.Id,
})
require.Nil(t, err)
deletedBot, err = th.App.UpdateBotActive(deletedBot.UserId, false)
require.Nil(t, err)
defer th.App.PermanentDeleteBot(deletedBot.UserId)
t.Run("get unknown bot", func(t *testing.T) {
_, err := th.App.GetBot(model.NewId(), false)
require.NotNil(t, err)
require.Equal(t, "store.sql_bot.get.missing.app_error", err.Id)
})
t.Run("get bot1", func(t *testing.T) {
bot, err := th.App.GetBot(bot1.UserId, false)
require.Nil(t, err)
assert.Equal(t, bot1, bot)
})
t.Run("get bot2", func(t *testing.T) {
bot, err := th.App.GetBot(bot2.UserId, false)
require.Nil(t, err)
assert.Equal(t, bot2, bot)
})
t.Run("get deleted bot", func(t *testing.T) {
_, err := th.App.GetBot(deletedBot.UserId, false)
require.NotNil(t, err)
require.Equal(t, "store.sql_bot.get.missing.app_error", err.Id)
})
t.Run("get deleted bot, include deleted", func(t *testing.T) {
bot, err := th.App.GetBot(deletedBot.UserId, true)
require.Nil(t, err)
assert.Equal(t, deletedBot, bot)
})
}
func TestGetBots(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
OwnerId1 := model.NewId()
OwnerId2 := model.NewId()
bot1, err := th.App.CreateBot(&model.Bot{
Username: "username",
Description: "a bot",
OwnerId: OwnerId1,
})
require.Nil(t, err)
defer th.App.PermanentDeleteBot(bot1.UserId)
deletedBot1, err := th.App.CreateBot(&model.Bot{
Username: "username4",
Description: "a deleted bot",
OwnerId: OwnerId1,
})
require.Nil(t, err)
deletedBot1, err = th.App.UpdateBotActive(deletedBot1.UserId, false)
require.Nil(t, err)
defer th.App.PermanentDeleteBot(deletedBot1.UserId)
bot2, err := th.App.CreateBot(&model.Bot{
Username: "username2",
Description: "a second bot",
OwnerId: OwnerId1,
})
require.Nil(t, err)
defer th.App.PermanentDeleteBot(bot2.UserId)
bot3, err := th.App.CreateBot(&model.Bot{
Username: "username3",
Description: "a third bot",
OwnerId: OwnerId1,
})
require.Nil(t, err)
defer th.App.PermanentDeleteBot(bot3.UserId)
bot4, err := th.App.CreateBot(&model.Bot{
Username: "username5",
Description: "a fourth bot",
OwnerId: OwnerId2,
})
require.Nil(t, err)
defer th.App.PermanentDeleteBot(bot4.UserId)
deletedBot2, err := th.App.CreateBot(&model.Bot{
Username: "username6",
Description: "a deleted bot",
OwnerId: OwnerId2,
})
require.Nil(t, err)
deletedBot2, err = th.App.UpdateBotActive(deletedBot2.UserId, false)
require.Nil(t, err)
defer th.App.PermanentDeleteBot(deletedBot2.UserId)
t.Run("get bots, page=0, perPage=10", func(t *testing.T) {
bots, err := th.App.GetBots(&model.BotGetOptions{
Page: 0,
PerPage: 10,
OwnerId: "",
IncludeDeleted: false,
})
require.Nil(t, err)
assert.Equal(t, model.BotList{bot1, bot2, bot3, bot4}, bots)
})
t.Run("get bots, page=0, perPage=1", func(t *testing.T) {
bots, err := th.App.GetBots(&model.BotGetOptions{
Page: 0,
PerPage: 1,
OwnerId: "",
IncludeDeleted: false,
})
require.Nil(t, err)
assert.Equal(t, model.BotList{bot1}, bots)
})
t.Run("get bots, page=1, perPage=2", func(t *testing.T) {
bots, err := th.App.GetBots(&model.BotGetOptions{
Page: 1,
PerPage: 2,
OwnerId: "",
IncludeDeleted: false,
})
require.Nil(t, err)
assert.Equal(t, model.BotList{bot3, bot4}, bots)
})
t.Run("get bots, page=2, perPage=2", func(t *testing.T) {
bots, err := th.App.GetBots(&model.BotGetOptions{
Page: 2,
PerPage: 2,
OwnerId: "",
IncludeDeleted: false,
})
require.Nil(t, err)
assert.Equal(t, model.BotList{}, bots)
})
t.Run("get bots, page=0, perPage=10, include deleted", func(t *testing.T) {
bots, err := th.App.GetBots(&model.BotGetOptions{
Page: 0,
PerPage: 10,
OwnerId: "",
IncludeDeleted: true,
})
require.Nil(t, err)
assert.Equal(t, model.BotList{bot1, deletedBot1, bot2, bot3, bot4, deletedBot2}, bots)
})
t.Run("get bots, page=0, perPage=1, include deleted", func(t *testing.T) {
bots, err := th.App.GetBots(&model.BotGetOptions{
Page: 0,
PerPage: 1,
OwnerId: "",
IncludeDeleted: true,
})
require.Nil(t, err)
assert.Equal(t, model.BotList{bot1}, bots)
})
t.Run("get bots, page=1, perPage=2, include deleted", func(t *testing.T) {
bots, err := th.App.GetBots(&model.BotGetOptions{
Page: 1,
PerPage: 2,
OwnerId: "",
IncludeDeleted: true,
})
require.Nil(t, err)
assert.Equal(t, model.BotList{bot2, bot3}, bots)
})
t.Run("get bots, page=2, perPage=2, include deleted", func(t *testing.T) {
bots, err := th.App.GetBots(&model.BotGetOptions{
Page: 2,
PerPage: 2,
OwnerId: "",
IncludeDeleted: true,
})
require.Nil(t, err)
assert.Equal(t, model.BotList{bot4, deletedBot2}, bots)
})
t.Run("get offset=0, limit=10, creator id 1", func(t *testing.T) {
bots, err := th.App.GetBots(&model.BotGetOptions{
Page: 0,
PerPage: 10,
OwnerId: OwnerId1,
IncludeDeleted: false,
})
require.Nil(t, err)
require.Equal(t, model.BotList{bot1, bot2, bot3}, bots)
})
t.Run("get offset=0, limit=10, creator id 2", func(t *testing.T) {
bots, err := th.App.GetBots(&model.BotGetOptions{
Page: 0,
PerPage: 10,
OwnerId: OwnerId2,
IncludeDeleted: false,
})
require.Nil(t, err)
require.Equal(t, model.BotList{bot4}, bots)
})
t.Run("get offset=0, limit=10, include deleted, creator id 1", func(t *testing.T) {
bots, err := th.App.GetBots(&model.BotGetOptions{
Page: 0,
PerPage: 10,
OwnerId: OwnerId1,
IncludeDeleted: true,
})
require.Nil(t, err)
require.Equal(t, model.BotList{bot1, deletedBot1, bot2, bot3}, bots)
})
t.Run("get offset=0, limit=10, include deleted, creator id 2", func(t *testing.T) {
bots, err := th.App.GetBots(&model.BotGetOptions{
Page: 0,
PerPage: 10,
OwnerId: OwnerId2,
IncludeDeleted: true,
})
require.Nil(t, err)
require.Equal(t, model.BotList{bot4, deletedBot2}, bots)
})
}
func TestUpdateBotActive(t *testing.T) {
t.Run("unknown bot", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
_, err := th.App.UpdateBotActive(model.NewId(), false)
require.NotNil(t, err)
require.Equal(t, "store.sql_user.missing_account.const", err.Id)
})
t.Run("disable/enable bot", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
bot, err := th.App.CreateBot(&model.Bot{
Username: "username",
Description: "a bot",
OwnerId: th.BasicUser.Id,
})
require.Nil(t, err)
defer th.App.PermanentDeleteBot(bot.UserId)
disabledBot, err := th.App.UpdateBotActive(bot.UserId, false)
require.Nil(t, err)
require.NotEqual(t, 0, disabledBot.DeleteAt)
// Disabling should be idempotent
disabledBotAgain, err := th.App.UpdateBotActive(bot.UserId, false)
require.Nil(t, err)
require.Equal(t, disabledBot.DeleteAt, disabledBotAgain.DeleteAt)
reenabledBot, err := th.App.UpdateBotActive(bot.UserId, true)
require.Nil(t, err)
require.EqualValues(t, 0, reenabledBot.DeleteAt)
// Re-enabling should be idempotent
reenabledBotAgain, err := th.App.UpdateBotActive(bot.UserId, true)
require.Nil(t, err)
require.Equal(t, reenabledBot.DeleteAt, reenabledBotAgain.DeleteAt)
})
}
func TestPermanentDeleteBot(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
bot, err := th.App.CreateBot(&model.Bot{
Username: "username",
Description: "a bot",
OwnerId: th.BasicUser.Id,
})
require.Nil(t, err)
require.Nil(t, th.App.PermanentDeleteBot(bot.UserId))
_, err = th.App.GetBot(bot.UserId, false)
require.NotNil(t, err)
require.Equal(t, "store.sql_bot.get.missing.app_error", err.Id)
}
func TestDisableUserBots(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
ownerId1 := model.NewId()
ownerId2 := model.NewId()
bots := []*model.Bot{}
defer func() {
for _, bot := range bots {
th.App.PermanentDeleteBot(bot.UserId)
}
}()
for i := 0; i < 46; i++ {
bot, err := th.App.CreateBot(&model.Bot{
Username: fmt.Sprintf("username%v", i),
Description: "a bot",
OwnerId: ownerId1,
})
require.Nil(t, err)
bots = append(bots, bot)
}
require.Len(t, bots, 46)
u2bot1, err := th.App.CreateBot(&model.Bot{
Username: "username_nodisable",
Description: "a bot",
OwnerId: ownerId2,
})
require.Nil(t, err)
defer th.App.PermanentDeleteBot(u2bot1.UserId)
err = th.App.disableUserBots(ownerId1)
require.Nil(t, err)
// Check all bots and corrensponding users are disabled for creator 1
for _, bot := range bots {
retbot, err2 := th.App.GetBot(bot.UserId, true)
require.Nil(t, err2)
require.NotZero(t, retbot.DeleteAt, bot.Username)
}
// Check bots and corresponding user not disabled for creator 2
bot, err := th.App.GetBot(u2bot1.UserId, true)
require.Nil(t, err)
require.Zero(t, bot.DeleteAt)
user, err := th.App.GetUser(u2bot1.UserId)
require.Nil(t, err)
require.Zero(t, user.DeleteAt)
// Bad id doesn't do anything or break horribly
err = th.App.disableUserBots(model.NewId())
require.Nil(t, err)
}
func sToP(s string) *string {
return &s
}

View File

@@ -122,6 +122,7 @@ func pluginActivated(pluginStates map[string]*model.PluginState, pluginId string
func (a *App) trackActivity() {
var userCount int64
var botAccountsCount int64
var activeUsersDailyCount int64
var activeUsersMonthlyCount int64
var inactiveUserCount int64
@@ -147,10 +148,19 @@ func (a *App) trackActivity() {
activeUsersMonthlyCount = r.Data.(int64)
}
if ucr := <-a.Srv.Store.User().GetTotalUsersCount(); ucr.Err == nil {
if ucr := <-a.Srv.Store.User().Count(model.UserCountOptions{
IncludeDeleted: true,
}); ucr.Err == nil {
userCount = ucr.Data.(int64)
}
if bc := <-a.Srv.Store.User().Count(model.UserCountOptions{
IncludeBotAccounts: true,
ExcludeRegularUsers: true,
}); bc.Err == nil {
botAccountsCount = bc.Data.(int64)
}
if iucr := <-a.Srv.Store.User().AnalyticsGetInactiveUsersCount(); iucr.Err == nil {
inactiveUserCount = iucr.Data.(int64)
}
@@ -197,6 +207,7 @@ func (a *App) trackActivity() {
a.SendDiagnostic(TRACK_ACTIVITY, map[string]interface{}{
"registered_users": userCount,
"bot_accounts": botAccountsCount,
"active_users_daily": activeUsersDailyCount,
"active_users_monthly": activeUsersMonthlyCount,
"registered_deactivated_users": inactiveUserCount,
@@ -281,6 +292,7 @@ func (a *App) trackConfig() {
"enable_email_invitations": *cfg.ServiceSettings.EnableEmailInvitations,
"experimental_channel_organization": *cfg.ServiceSettings.ExperimentalChannelOrganization,
"experimental_ldap_group_sync": *cfg.ServiceSettings.ExperimentalLdapGroupSync,
"disable_bots_when_owner_is_deactivated": *cfg.ServiceSettings.DisableBotsWhenOwnerIsDeactivated,
})
a.SendDiagnostic(TRACK_CONFIG_TEAM, map[string]interface{}{

View File

@@ -553,7 +553,10 @@ func TestImportImportUser(t *testing.T) {
// Check how many users are in the database.
var userCount int64
if r := <-th.App.Srv.Store.User().GetTotalUsersCount(); r.Err == nil {
if r := <-th.App.Srv.Store.User().Count(model.UserCountOptions{
IncludeDeleted: true,
IncludeBotAccounts: false,
}); r.Err == nil {
userCount = r.Data.(int64)
} else {
t.Fatalf("Failed to get user count.")
@@ -568,7 +571,10 @@ func TestImportImportUser(t *testing.T) {
}
// Check that no more users are in the DB.
if r := <-th.App.Srv.Store.User().GetTotalUsersCount(); r.Err == nil {
if r := <-th.App.Srv.Store.User().Count(model.UserCountOptions{
IncludeDeleted: true,
IncludeBotAccounts: false,
}); r.Err == nil {
if r.Data.(int64) != userCount {
t.Fatalf("Unexpected number of users")
}
@@ -586,7 +592,10 @@ func TestImportImportUser(t *testing.T) {
}
// Check that no more users are in the DB.
if r := <-th.App.Srv.Store.User().GetTotalUsersCount(); r.Err == nil {
if r := <-th.App.Srv.Store.User().Count(model.UserCountOptions{
IncludeDeleted: true,
IncludeBotAccounts: false,
}); r.Err == nil {
if r.Data.(int64) != userCount {
t.Fatalf("Unexpected number of users")
}
@@ -603,7 +612,10 @@ func TestImportImportUser(t *testing.T) {
}
// Check that no more users are in the DB.
if r := <-th.App.Srv.Store.User().GetTotalUsersCount(); r.Err == nil {
if r := <-th.App.Srv.Store.User().Count(model.UserCountOptions{
IncludeDeleted: true,
IncludeBotAccounts: false,
}); r.Err == nil {
if r.Data.(int64) != userCount {
t.Fatalf("Unexpected number of users")
}
@@ -628,7 +640,10 @@ func TestImportImportUser(t *testing.T) {
}
// Check that one more user is in the DB.
if r := <-th.App.Srv.Store.User().GetTotalUsersCount(); r.Err == nil {
if r := <-th.App.Srv.Store.User().Count(model.UserCountOptions{
IncludeDeleted: true,
IncludeBotAccounts: false,
}); r.Err == nil {
if r.Data.(int64) != userCount+1 {
t.Fatalf("Unexpected number of users")
}
@@ -685,7 +700,10 @@ func TestImportImportUser(t *testing.T) {
}
// Check user count the same.
if r := <-th.App.Srv.Store.User().GetTotalUsersCount(); r.Err == nil {
if r := <-th.App.Srv.Store.User().Count(model.UserCountOptions{
IncludeDeleted: true,
IncludeBotAccounts: false,
}); r.Err == nil {
if r.Data.(int64) != userCount+1 {
t.Fatalf("Unexpected number of users")
}

View File

@@ -52,7 +52,7 @@ func (a *App) SaveLicense(licenseBytes []byte) (*model.License, *model.AppError)
}
license := model.LicenseFromJson(strings.NewReader(licenseStr))
result := <-a.Srv.Store.User().AnalyticsUniqueUserCount("")
result := <-a.Srv.Store.User().Count(model.UserCountOptions{})
if result.Err != nil {
return nil, model.NewAppError("addLicense", "api.license.add_license.invalid_count.app_error", nil, result.Err.Error(), http.StatusBadRequest)
}

View File

@@ -57,6 +57,11 @@ func (a *App) AuthenticateUserForLogin(id, loginId, password, mfaToken string, l
// then trust the proxy and cert that the correct user is supplied and allow
// them access
if *a.Config().ExperimentalSettings.ClientSideCertEnable && *a.Config().ExperimentalSettings.ClientSideCertCheck == model.CLIENT_SIDE_CERT_CHECK_PRIMARY_AUTH {
// Unless the user is a bot.
if err = checkUserNotBot(user); err != nil {
return nil, err
}
return user, nil
}

View File

@@ -551,6 +551,13 @@ func (a *App) LoginByOAuth(service string, userData io.Reader, teamId string) (*
return nil, err
}
} else {
// OAuth doesn't run through CheckUserPreflightAuthenticationCriteria, so prevent bot login
// here manually. Technically, the auth data above will fail to match a bot in the first
// place, but explicit is always better.
if user.IsBot {
return nil, model.NewAppError("loginByOAuth", "api.user.login_by_oauth.bot_login_forbidden.app_error", nil, "", http.StatusForbidden)
}
if err = a.UpdateOAuthUserAttrs(bytes.NewReader(buf.Bytes()), user, provider, service); err != nil {
return nil, err
}

View File

@@ -686,3 +686,35 @@ func (api *PluginAPI) LogError(msg string, keyValuePairs ...interface{}) {
func (api *PluginAPI) LogWarn(msg string, keyValuePairs ...interface{}) {
api.logger.Warn(msg, keyValuePairs...)
}
func (api *PluginAPI) CreateBot(bot *model.Bot) (*model.Bot, *model.AppError) {
// Bots created by a plugin should use the plugin's ID for the creator field, unless
// otherwise specified by the plugin.
if bot.OwnerId == "" {
bot.OwnerId = api.id
}
return api.app.CreateBot(bot)
}
func (api *PluginAPI) PatchBot(userId string, botPatch *model.BotPatch) (*model.Bot, *model.AppError) {
return api.app.PatchBot(userId, botPatch)
}
func (api *PluginAPI) GetBot(userId string, includeDeleted bool) (*model.Bot, *model.AppError) {
return api.app.GetBot(userId, includeDeleted)
}
func (api *PluginAPI) GetBots(options *model.BotGetOptions) ([]*model.Bot, *model.AppError) {
bots, err := api.app.GetBots(options)
return []*model.Bot(bots), err
}
func (api *PluginAPI) UpdateBotActive(userId string, active bool) (*model.Bot, *model.AppError) {
return api.app.UpdateBotActive(userId, active)
}
func (api *PluginAPI) PermanentDeleteBot(userId string) *model.AppError {
return api.app.PermanentDeleteBot(userId)
}

View File

@@ -882,6 +882,151 @@ func TestPluginAPI_SearchTeams(t *testing.T) {
})
}
func TestPluginBots(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
setupPluginApiTest(t,
`
package main
import (
"github.com/mattermost/mattermost-server/plugin"
"github.com/mattermost/mattermost-server/model"
)
type MyPlugin struct {
plugin.MattermostPlugin
}
func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
createdBot, err := p.API.CreateBot(&model.Bot{
Username: "bot",
Description: "a plugin bot",
})
if err != nil {
return nil, err.Error() + "failed to create bot"
}
fetchedBot, err := p.API.GetBot(createdBot.UserId, false)
if err != nil {
return nil, err.Error() + "failed to get bot"
}
if fetchedBot.Description != "a plugin bot" {
return nil, "GetBot did not return the expected bot Description"
}
if fetchedBot.OwnerId != "testpluginbots" {
return nil, "GetBot did not return the expected bot OwnerId"
}
updatedDescription := createdBot.Description + ", updated"
patchedBot, err := p.API.PatchBot(createdBot.UserId, &model.BotPatch{
Description: &updatedDescription,
})
if err != nil {
return nil, err.Error() + "failed to patch bot"
}
fetchedBot, err = p.API.GetBot(patchedBot.UserId, false)
if err != nil {
return nil, err.Error() + "failed to get bot"
}
if fetchedBot.UserId != patchedBot.UserId {
return nil, "GetBot did not return the expected bot"
}
if fetchedBot.Description != "a plugin bot, updated" {
return nil, "GetBot did not return the updated bot Description"
}
fetchedBots, err := p.API.GetBots(&model.BotGetOptions{
Page: 0,
PerPage: 1,
OwnerId: "",
IncludeDeleted: false,
})
if err != nil {
return nil, err.Error() + "failed to get bots"
}
if len(fetchedBots) != 1 {
return nil, "GetBots did not return a single bot"
}
if fetchedBot.UserId != fetchedBots[0].UserId {
return nil, "GetBots did not return the expected bot"
}
_, err = p.API.UpdateBotActive(fetchedBot.UserId, false)
if err != nil {
return nil, err.Error() + "failed to disable bot"
}
fetchedBot, err = p.API.GetBot(patchedBot.UserId, false)
if err == nil {
return nil, "expected not to find disabled bot"
}
_, err = p.API.UpdateBotActive(fetchedBot.UserId, true)
if err != nil {
return nil, err.Error() + "failed to disable bot"
}
fetchedBot, err = p.API.GetBot(patchedBot.UserId, false)
if err != nil {
return nil, err.Error() + "failed to get bot after enabling"
}
if fetchedBot.UserId != patchedBot.UserId {
return nil, "GetBot did not return the expected bot after enabling"
}
err = p.API.PermanentDeleteBot(patchedBot.UserId)
if err != nil {
return nil, err.Error() + "failed to delete bot"
}
_, err = p.API.GetBot(patchedBot.UserId, false)
if err == nil {
return nil, err.Error() + "found bot after permanently deleting"
}
createdBotWithOverriddenCreator, err := p.API.CreateBot(&model.Bot{
Username: "bot",
Description: "a plugin bot",
OwnerId: "abc123",
})
if err != nil {
return nil, err.Error() + "failed to create bot with overridden creator"
}
fetchedBot, err = p.API.GetBot(createdBotWithOverriddenCreator.UserId, false)
if err != nil {
return nil, err.Error() + "failed to get bot"
}
if fetchedBot.Description != "a plugin bot" {
return nil, "GetBot did not return the expected bot Description"
}
if fetchedBot.OwnerId != "abc123" {
return nil, "GetBot did not return the expected bot OwnerId"
}
return nil, ""
}
func main() {
plugin.ClientMain(&MyPlugin{})
}
`,
`{"id": "testpluginbots", "backend": {"executable": "backend.exe"}}`,
"testpluginbots",
th.App,
)
hooks, err := th.App.GetPluginsEnvironment().HooksForPlugin("testpluginbots")
assert.NoError(t, err)
_, errString := hooks.MessageWillBePosted(nil, nil)
assert.Empty(t, errString)
}
func TestPluginAPI_GetTeamMembersForUser(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()

View File

@@ -63,7 +63,9 @@ func (s *Server) DoSecurityUpdateCheck() {
<-s.Store.System().Update(systemSecurityLastTime)
}
if ucr := <-s.Store.User().GetTotalUsersCount(); ucr.Err == nil {
if ucr := <-s.Store.User().Count(model.UserCountOptions{
IncludeDeleted: true,
}); ucr.Err == nil {
v.Set(PROP_SECURITY_USER_COUNT, strconv.FormatInt(ucr.Data.(int64), 10))
}

View File

@@ -171,7 +171,9 @@ func (a *App) IsUserSignUpAllowed() *model.AppError {
func (a *App) IsFirstUserAccount() bool {
if a.SessionCacheLength() == 0 {
cr := <-a.Srv.Store.User().GetTotalUsersCount()
cr := <-a.Srv.Store.User().Count(model.UserCountOptions{
IncludeDeleted: true,
})
if cr.Err != nil {
mlog.Error(fmt.Sprint(cr.Err))
return false
@@ -195,7 +197,9 @@ func (a *App) CreateUser(user *model.User) (*model.User, *model.AppError) {
// Below is a special case where the first user in the entire
// system is granted the system_admin role
result := <-a.Srv.Store.User().GetTotalUsersCount()
result := <-a.Srv.Store.User().Count(model.UserCountOptions{
IncludeDeleted: true,
})
if result.Err != nil {
return nil, result.Err
}
@@ -867,6 +871,40 @@ func (a *App) UpdatePasswordAsUser(userId, currentPassword, newPassword string)
return a.UpdatePasswordSendEmail(user, newPassword, T("api.user.update_password.menu"))
}
func (a *App) userDeactivated(user *model.User) *model.AppError {
if err := a.RevokeAllSessions(user.Id); err != nil {
return err
}
a.SetStatusOffline(user.Id, false)
if *a.Config().ServiceSettings.DisableBotsWhenOwnerIsDeactivated {
a.disableUserBots(user.Id)
}
return nil
}
func (a *App) invalidateUserChannelMembersCaches(user *model.User) *model.AppError {
teamsForUser, err := a.GetTeamsForUser(user.Id)
if err != nil {
return err
}
for _, team := range teamsForUser {
channelsForUser, err := a.GetChannelsForUser(team.Id, user.Id, false)
if err != nil {
return err
}
for _, channel := range *channelsForUser {
a.InvalidateCacheForChannelMembers(channel.Id)
}
}
return nil
}
func (a *App) UpdateActive(user *model.User, active bool) (*model.User, *model.AppError) {
if active {
user.DeleteAt = 0
@@ -878,35 +916,16 @@ func (a *App) UpdateActive(user *model.User, active bool) (*model.User, *model.A
if result.Err != nil {
return nil, result.Err
}
if user.DeleteAt > 0 {
if err := a.RevokeAllSessions(user.Id); err != nil {
return nil, err
}
}
ruser := result.Data.([2]*model.User)[0]
if !active {
a.SetStatusOffline(ruser.Id, false)
}
teamsForUser, err := a.GetTeamsForUser(user.Id)
if err != nil {
return nil, err
}
for _, team := range teamsForUser {
channelsForUser, err := a.GetChannelsForUser(team.Id, user.Id, false)
if err != nil {
if err := a.userDeactivated(ruser); err != nil {
return nil, err
}
for _, channel := range *channelsForUser {
a.InvalidateCacheForChannelMembers(channel.Id)
}
}
a.invalidateUserChannelMembersCaches(user)
a.sendUpdatedUserEvent(*ruser)
return ruser, nil
@@ -1500,8 +1519,11 @@ func (a *App) GetVerifyEmailToken(token string) (*model.Token, *model.AppError)
return rtoken, nil
}
// GetTotalUsersStats is used for the DM list total
func (a *App) GetTotalUsersStats() (*model.UsersStats, *model.AppError) {
result := <-a.Srv.Store.User().GetTotalUsersCount()
result := <-a.Srv.Store.User().Count(model.UserCountOptions{
IncludeBotAccounts: true,
})
if result.Err != nil {
return nil, result.Err
}

View File

@@ -172,6 +172,51 @@ func TestUpdateUserActive(t *testing.T) {
assert.Nil(t, err)
}
func TestUpdateActiveBotsSideEffect(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
bot, err := th.App.CreateBot(&model.Bot{
Username: "username",
Description: "a bot",
OwnerId: th.BasicUser.Id,
})
require.Nil(t, err)
defer th.App.PermanentDeleteBot(bot.UserId)
// Automatic deactivation disabled
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.DisableBotsWhenOwnerIsDeactivated = false
})
th.App.UpdateActive(th.BasicUser, false)
retbot1, err := th.App.GetBot(bot.UserId, true)
require.Nil(t, err)
require.Zero(t, retbot1.DeleteAt)
user1, err := th.App.GetUser(bot.UserId)
require.Nil(t, err)
require.Zero(t, user1.DeleteAt)
th.App.UpdateActive(th.BasicUser, true)
// Automatic deactivation enabled
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.DisableBotsWhenOwnerIsDeactivated = true
})
th.App.UpdateActive(th.BasicUser, false)
retbot2, err := th.App.GetBot(bot.UserId, true)
require.Nil(t, err)
require.NotZero(t, retbot2.DeleteAt)
user2, err := th.App.GetUser(bot.UserId)
require.Nil(t, err)
require.NotZero(t, user2.DeleteAt)
th.App.UpdateActive(th.BasicUser, true)
}
func TestUpdateOAuthUserAttrs(t *testing.T) {
th := Setup(t)
defer th.TearDown()
@@ -292,7 +337,7 @@ func TestUpdateUserEmail(t *testing.T) {
user := th.CreateUser()
t.Run("RequireVerification", func(t *testing.T){
t.Run("RequireVerification", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.EmailSettings.RequireEmailVerification = true
})
@@ -318,7 +363,7 @@ func TestUpdateUserEmail(t *testing.T) {
assert.True(t, user2.EmailVerified)
})
t.Run("RequireVerificationAlreadyUsedEmail", func(t *testing.T){
t.Run("RequireVerificationAlreadyUsedEmail", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.EmailSettings.RequireEmailVerification = true
})
@@ -332,7 +377,7 @@ func TestUpdateUserEmail(t *testing.T) {
assert.Nil(t, user3)
})
t.Run("NoVerification", func(t *testing.T){
t.Run("NoVerification", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.EmailSettings.RequireEmailVerification = false
})
@@ -648,7 +693,7 @@ func TestPasswordRecovery(t *testing.T) {
token, err = th.App.CreatePasswordRecoveryToken(th.BasicUser.Id, th.BasicUser.Email)
assert.Nil(t, err)
th.App.UpdateConfig(func (c *model.Config){
th.App.UpdateConfig(func(c *model.Config) {
*c.EmailSettings.RequireEmailVerification = false
})
@@ -659,4 +704,3 @@ func TestPasswordRecovery(t *testing.T) {
err = th.App.ResetPasswordFromToken(token.Token, "abcdefgh")
assert.NotNil(t, err)
}

View File

@@ -2258,6 +2258,10 @@
"id": "api.user.ldap_to_email.not_ldap_account.app_error",
"translation": "This user account does not use AD/LDAP"
},
{
"id": "api.user.login.bot_login_forbidden.app_error",
"translation": "Bot login is forbidden"
},
{
"id": "api.user.login.blank_pwd.app_error",
"translation": "Password field must not be blank"
@@ -2286,6 +2290,10 @@
"id": "api.user.login.use_auth_service.app_error",
"translation": "Please sign in using {{.AuthService}}"
},
{
"id": "api.user.login_by_oauth.bot_login_forbidden.app_error",
"translation": "Bot login is forbidden"
},
{
"id": "api.user.login_by_oauth.not_available.app_error",
"translation": "{{.Service}} SSO through OAuth 2.0 not available on this server"
@@ -3830,6 +3838,30 @@
"id": "model.authorize.is_valid.user_id.app_error",
"translation": "Invalid user id"
},
{
"id": "model.bot.is_valid.create_at.app_error",
"translation": "Invalid create at"
},
{
"id": "model.bot.is_valid.creator_id.app_error",
"translation": "Invalid creator id"
},
{
"id": "model.bot.is_valid.description.app_error",
"translation": "Invalid description"
},
{
"id": "model.bot.is_valid.update_at.app_error",
"translation": "Invalid update at"
},
{
"id": "model.bot.is_valid.user_id.app_error",
"translation": "Invalid user id"
},
{
"id": "model.bot.is_valid.username.app_error",
"translation": "Invalid username"
},
{
"id": "model.channel.is_valid.2_or_more.app_error",
"translation": "Name must be 2 or more lowercase alphanumeric characters"
@@ -5026,6 +5058,34 @@
"id": "store.sql_audit.save.saving.app_error",
"translation": "We encountered an error saving the audit"
},
{
"id": "store.sql_bot.delete.app_error",
"translation": "Unable to delete the bot"
},
{
"id": "store.sql_bot.get.app_error",
"translation": "Unable to get the bot"
},
{
"id": "store.sql_bot.get.missing.app_error",
"translation": "Bot does not exist"
},
{
"id": "store.sql_bot.get_all.app_error",
"translation": "Unable to get the bots"
},
{
"id": "store.sql_bot.save.app_error",
"translation": "Unable to save the bot"
},
{
"id": "store.sql_bot.update.app_error",
"translation": "Unable to update the bot"
},
{
"id": "store.sql_bot.update.updating.app_error",
"translation": "We encountered an error updating the bot"
},
{
"id": "store.sql_channel.analytics_deleted_type_count.app_error",
"translation": "Unable to get deleted channel type counts"

209
model/bot.go Normal file
View File

@@ -0,0 +1,209 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"unicode/utf8"
)
const (
BOT_DISPLAY_NAME_MAX_RUNES = USER_FIRST_NAME_MAX_RUNES
BOT_DESCRIPTION_MAX_RUNES = 1024
BOT_CREATOR_ID_MAX_RUNES = KEY_VALUE_PLUGIN_ID_MAX_RUNES // UserId or PluginId
)
// Bot is a special type of User meant for programmatic interactions.
// Note that the primary key of a bot is the UserId, and matches the primary key of the
// corresponding user.
type Bot struct {
UserId string `json:"user_id"`
Username string `json:"username"`
DisplayName string `json:"display_name,omitempty"`
Description string `json:"description,omitempty"`
OwnerId string `json:"creator_id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
}
// BotPatch is a description of what fields to update on an existing bot.
type BotPatch struct {
Username *string `json:"username"`
DisplayName *string `json:"display_name"`
Description *string `json:"description"`
}
// BotGetOptions acts as a filter on bulk bot fetching queries.
type BotGetOptions struct {
OwnerId string
IncludeDeleted bool
OnlyOrphaned bool
Page int
PerPage int
}
// BotList is a list of bots.
type BotList []*Bot
// Trace describes the minimum information required to identify a bot for the purpose of logging.
func (b *Bot) Trace() map[string]interface{} {
return map[string]interface{}{"user_id": b.UserId}
}
// Clone returns a shallow copy of the bot.
func (b *Bot) Clone() *Bot {
copy := *b
return &copy
}
// IsValid validates the bot and returns an error if it isn't configured correctly.
func (b *Bot) IsValid() *AppError {
if !IsValidId(b.UserId) {
return NewAppError("Bot.IsValid", "model.bot.is_valid.user_id.app_error", b.Trace(), "", http.StatusBadRequest)
}
if !IsValidUsername(b.Username) {
return NewAppError("Bot.IsValid", "model.bot.is_valid.username.app_error", b.Trace(), "", http.StatusBadRequest)
}
if utf8.RuneCountInString(b.DisplayName) > BOT_DISPLAY_NAME_MAX_RUNES {
return NewAppError("Bot.IsValid", "model.bot.is_valid.user_id.app_error", b.Trace(), "", http.StatusBadRequest)
}
if utf8.RuneCountInString(b.Description) > BOT_DESCRIPTION_MAX_RUNES {
return NewAppError("Bot.IsValid", "model.bot.is_valid.description.app_error", b.Trace(), "", http.StatusBadRequest)
}
if len(b.OwnerId) == 0 || utf8.RuneCountInString(b.OwnerId) > BOT_CREATOR_ID_MAX_RUNES {
return NewAppError("Bot.IsValid", "model.bot.is_valid.creator_id.app_error", b.Trace(), "", http.StatusBadRequest)
}
if b.CreateAt == 0 {
return NewAppError("Bot.IsValid", "model.bot.is_valid.create_at.app_error", b.Trace(), "", http.StatusBadRequest)
}
if b.UpdateAt == 0 {
return NewAppError("Bot.IsValid", "model.bot.is_valid.update_at.app_error", b.Trace(), "", http.StatusBadRequest)
}
return nil
}
// PreSave should be run before saving a new bot to the database.
func (b *Bot) PreSave() {
b.CreateAt = GetMillis()
b.UpdateAt = b.CreateAt
b.DeleteAt = 0
}
// PreUpdate should be run before saving an updated bot to the database.
func (b *Bot) PreUpdate() {
b.UpdateAt = GetMillis()
}
// Etag generates an etag for caching.
func (b *Bot) Etag() string {
return Etag(b.UserId, b.UpdateAt)
}
// ToJson serializes the bot to json.
func (b *Bot) ToJson() []byte {
data, _ := json.Marshal(b)
return data
}
// BotFromJson deserializes a bot from json.
func BotFromJson(data io.Reader) *Bot {
var bot *Bot
json.NewDecoder(data).Decode(&bot)
return bot
}
// Patch modifies an existing bot with optional fields from the given patch.
func (b *Bot) Patch(patch *BotPatch) {
if patch.Username != nil {
b.Username = *patch.Username
}
if patch.DisplayName != nil {
b.DisplayName = *patch.DisplayName
}
if patch.Description != nil {
b.Description = *patch.Description
}
}
// ToJson serializes the bot patch to json.
func (b *BotPatch) ToJson() []byte {
data, err := json.Marshal(b)
if err != nil {
return nil
}
return data
}
// BotPatchFromJson deserializes a bot patch from json.
func BotPatchFromJson(data io.Reader) *BotPatch {
decoder := json.NewDecoder(data)
var botPatch BotPatch
err := decoder.Decode(&botPatch)
if err != nil {
return nil
}
return &botPatch
}
// UserFromBot returns a user model describing the bot fields stored in the User store.
func UserFromBot(b *Bot) *User {
return &User{
Id: b.UserId,
Username: b.Username,
Email: fmt.Sprintf("%s@localhost", strings.ToLower(b.Username)),
FirstName: b.DisplayName,
}
}
// BotListFromJson deserializes a list of bots from json.
func BotListFromJson(data io.Reader) BotList {
var bots BotList
json.NewDecoder(data).Decode(&bots)
return bots
}
// ToJson serializes a list of bots to json.
func (l *BotList) ToJson() []byte {
b, _ := json.Marshal(l)
return b
}
// Etag computes the etag for a list of bots.
func (l *BotList) Etag() string {
id := "0"
var t int64 = 0
var delta int64 = 0
for _, v := range *l {
if v.UpdateAt > t {
t = v.UpdateAt
id = v.UserId
}
}
return Etag(id, t, delta, len(*l))
}
// MakeBotNotFoundError creates the error returned when a bot does not exist, or when the user isn't allowed to query the bot.
// The errors must the same in both cases to avoid leaking that a user is a bot.
func MakeBotNotFoundError(userId string) *AppError {
return NewAppError("SqlBotStore.Get", "store.sql_bot.get.missing.app_error", map[string]interface{}{"user_id": userId}, "", http.StatusNotFound)
}

666
model/bot_test.go Normal file
View File

@@ -0,0 +1,666 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"bytes"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBotTrace(t *testing.T) {
bot := &Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
}
require.Equal(t, map[string]interface{}{"user_id": bot.UserId}, bot.Trace())
}
func TestBotClone(t *testing.T) {
bot := &Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
}
clone := bot.Clone()
require.Equal(t, bot, bot.Clone())
require.False(t, bot == clone)
}
func TestBotIsValid(t *testing.T) {
testCases := []struct {
Description string
Bot *Bot
ExpectedIsValid bool
}{
{
"nil bot",
&Bot{},
false,
},
{
"bot with missing user id",
&Bot{
UserId: "",
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
},
false,
},
{
"bot with invalid user id",
&Bot{
UserId: "invalid",
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
},
false,
},
{
"bot with missing username",
&Bot{
UserId: NewId(),
Username: "",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
},
false,
},
{
"bot with invalid username",
&Bot{
UserId: NewId(),
Username: "a@",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
},
false,
},
{
"bot with long description",
&Bot{
UserId: "",
Username: "username",
DisplayName: "display name",
Description: strings.Repeat("x", 1025),
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
},
false,
},
{
"bot with missing creator id",
&Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: "",
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
},
false,
},
{
"bot without create at timestamp",
&Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 0,
UpdateAt: 2,
DeleteAt: 3,
},
false,
},
{
"bot without update at timestamp",
&Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 0,
DeleteAt: 3,
},
false,
},
{
"bot",
&Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 0,
},
true,
},
{
"bot without description",
&Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "",
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 0,
},
true,
},
{
"deleted bot",
&Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "a description",
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
},
true,
},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
if testCase.ExpectedIsValid {
require.Nil(t, testCase.Bot.IsValid())
} else {
require.NotNil(t, testCase.Bot.IsValid())
}
})
}
}
func TestBotPreSave(t *testing.T) {
bot := &Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
DeleteAt: 0,
}
originalBot := &*bot
bot.PreSave()
assert.NotEqual(t, 0, bot.CreateAt)
assert.NotEqual(t, 0, bot.UpdateAt)
originalBot.CreateAt = bot.CreateAt
originalBot.UpdateAt = bot.UpdateAt
assert.Equal(t, originalBot, bot)
}
func TestBotPreUpdate(t *testing.T) {
bot := &Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 1,
DeleteAt: 0,
}
originalBot := &*bot
bot.PreSave()
assert.NotEqual(t, 0, bot.UpdateAt)
originalBot.UpdateAt = bot.UpdateAt
assert.Equal(t, originalBot, bot)
}
func TestBotEtag(t *testing.T) {
t.Run("same etags", func(t *testing.T) {
bot1 := &Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
}
bot2 := bot1
assert.Equal(t, bot1.Etag(), bot2.Etag())
})
t.Run("different etags", func(t *testing.T) {
t.Run("different user id", func(t *testing.T) {
bot1 := &Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
}
bot2 := &Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: bot1.OwnerId,
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
}
assert.NotEqual(t, bot1.Etag(), bot2.Etag())
})
t.Run("different update at", func(t *testing.T) {
bot1 := &Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
}
bot2 := &Bot{
UserId: bot1.UserId,
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: bot1.OwnerId,
CreateAt: 1,
UpdateAt: 10,
DeleteAt: 3,
}
assert.NotEqual(t, bot1.Etag(), bot2.Etag())
})
})
}
func TestBotToAndFromJson(t *testing.T) {
bot1 := &Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
}
bot2 := &Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description 2",
OwnerId: NewId(),
CreateAt: 4,
UpdateAt: 5,
DeleteAt: 6,
}
assert.Equal(t, bot1, BotFromJson(bytes.NewReader(bot1.ToJson())))
assert.Equal(t, bot2, BotFromJson(bytes.NewReader(bot2.ToJson())))
}
func sToP(s string) *string {
return &s
}
func TestBotPatch(t *testing.T) {
userId1 := NewId()
creatorId1 := NewId()
testCases := []struct {
Description string
Bot *Bot
BotPatch *BotPatch
ExpectedBot *Bot
}{
{
"no update",
&Bot{
UserId: userId1,
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: creatorId1,
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
},
&BotPatch{},
&Bot{
UserId: userId1,
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: creatorId1,
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
},
},
{
"partial update",
&Bot{
UserId: userId1,
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: creatorId1,
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
},
&BotPatch{
Username: sToP("new_username"),
DisplayName: nil,
Description: sToP("new description"),
},
&Bot{
UserId: userId1,
Username: "new_username",
DisplayName: "display name",
Description: "new description",
OwnerId: creatorId1,
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
},
},
{
"full update",
&Bot{
UserId: userId1,
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: creatorId1,
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
},
&BotPatch{
Username: sToP("new_username"),
DisplayName: sToP("new display name"),
Description: sToP("new description"),
},
&Bot{
UserId: userId1,
Username: "new_username",
DisplayName: "new display name",
Description: "new description",
OwnerId: creatorId1,
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
},
},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
testCase.Bot.Patch(testCase.BotPatch)
assert.Equal(t, testCase.ExpectedBot, testCase.Bot)
})
}
}
func TestBotPatchToAndFromJson(t *testing.T) {
botPatch1 := &BotPatch{
Username: sToP("username"),
DisplayName: sToP("display name"),
Description: sToP("description"),
}
botPatch2 := &BotPatch{
Username: sToP("username"),
DisplayName: sToP("display name"),
Description: sToP("description 2"),
}
assert.Equal(t, botPatch1, BotPatchFromJson(bytes.NewReader(botPatch1.ToJson())))
assert.Equal(t, botPatch2, BotPatchFromJson(bytes.NewReader(botPatch2.ToJson())))
}
func TestUserFromBot(t *testing.T) {
bot1 := &Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
}
bot2 := &Bot{
UserId: NewId(),
Username: "username2",
DisplayName: "display name 2",
Description: "description 2",
OwnerId: NewId(),
CreateAt: 4,
UpdateAt: 5,
DeleteAt: 6,
}
assert.Equal(t, &User{
Id: bot1.UserId,
Username: "username",
Email: "username@localhost",
FirstName: "display name",
}, UserFromBot(bot1))
assert.Equal(t, &User{
Id: bot2.UserId,
Username: "username2",
Email: "username2@localhost",
FirstName: "display name 2",
}, UserFromBot(bot2))
}
func TestBotListToAndFromJson(t *testing.T) {
testCases := []struct {
Description string
BotList BotList
}{
{
"empty list",
BotList{},
},
{
"single item",
BotList{
&Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
},
},
},
{
"multiple items",
BotList{
&Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
},
&Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description 2",
OwnerId: NewId(),
CreateAt: 4,
UpdateAt: 5,
DeleteAt: 6,
},
},
},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
assert.Equal(t, testCase.BotList, BotListFromJson(bytes.NewReader(testCase.BotList.ToJson())))
})
}
}
func TestBotListEtag(t *testing.T) {
bot1 := &Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 2,
DeleteAt: 3,
}
bot1Updated := &Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 1,
UpdateAt: 10,
DeleteAt: 3,
}
bot2 := &Bot{
UserId: NewId(),
Username: "username",
DisplayName: "display name",
Description: "description",
OwnerId: NewId(),
CreateAt: 4,
UpdateAt: 5,
DeleteAt: 6,
}
testCases := []struct {
Description string
BotListA BotList
BotListB BotList
ExpectedEqual bool
}{
{
"empty lists",
BotList{},
BotList{},
true,
},
{
"single item, same list",
BotList{bot1},
BotList{bot1},
true,
},
{
"single item, different update at",
BotList{bot1},
BotList{bot1Updated},
false,
},
{
"single item vs. multiple items",
BotList{bot1},
BotList{bot1, bot2},
false,
},
{
"multiple items, different update at",
BotList{bot1, bot2},
BotList{bot1Updated, bot2},
false,
},
{
"multiple items, same list",
BotList{bot1, bot2},
BotList{bot1, bot2},
true,
},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
if testCase.ExpectedEqual {
assert.Equal(t, testCase.BotListA.Etag(), testCase.BotListB.Etag())
} else {
assert.NotEqual(t, testCase.BotListA.Etag(), testCase.BotListB.Etag())
}
})
}
}

View File

@@ -150,6 +150,14 @@ func (c *Client4) GetUserByEmailRoute(email string) string {
return fmt.Sprintf(c.GetUsersRoute()+"/email/%v", email)
}
func (c *Client4) GetBotsRoute() string {
return fmt.Sprintf("/bots")
}
func (c *Client4) GetBotRoute(botUserId string) string {
return fmt.Sprintf("%s/%s", c.GetBotsRoute(), botUserId)
}
func (c *Client4) GetTeamsRoute() string {
return fmt.Sprintf("/teams")
}
@@ -442,6 +450,10 @@ func (c *Client4) DoApiPut(url string, data string) (*http.Response, *AppError)
return c.DoApiRequest(http.MethodPut, c.ApiUrl+url, data, "")
}
func (c *Client4) doApiPutBytes(url string, data []byte) (*http.Response, *AppError) {
return c.doApiRequestBytes(http.MethodPut, c.ApiUrl+url, data, "")
}
func (c *Client4) DoApiDelete(url string) (*http.Response, *AppError) {
return c.DoApiRequest(http.MethodDelete, c.ApiUrl+url, "", "")
}
@@ -1335,6 +1347,111 @@ func (c *Client4) EnableUserAccessToken(tokenId string) (bool, *Response) {
return CheckStatusOK(r), BuildResponse(r)
}
// Bots section
// CreateBot creates a bot in the system based on the provided bot struct.
func (c *Client4) CreateBot(bot *Bot) (*Bot, *Response) {
r, err := c.doApiPostBytes(c.GetBotsRoute(), bot.ToJson())
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return BotFromJson(r.Body), BuildResponse(r)
}
// PatchBot partially updates a bot. Any missing fields are not updated.
func (c *Client4) PatchBot(userId string, patch *BotPatch) (*Bot, *Response) {
r, err := c.doApiPutBytes(c.GetBotRoute(userId), patch.ToJson())
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return BotFromJson(r.Body), BuildResponse(r)
}
// GetBot fetches the given, undeleted bot.
func (c *Client4) GetBot(userId string, etag string) (*Bot, *Response) {
r, err := c.DoApiGet(c.GetBotRoute(userId), etag)
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return BotFromJson(r.Body), BuildResponse(r)
}
// GetBot fetches the given bot, even if it is deleted.
func (c *Client4) GetBotIncludeDeleted(userId string, etag string) (*Bot, *Response) {
r, err := c.DoApiGet(c.GetBotRoute(userId)+"?include_deleted=true", etag)
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return BotFromJson(r.Body), BuildResponse(r)
}
// GetBots fetches the given page of bots, excluding deleted.
func (c *Client4) GetBots(page, perPage int, etag string) ([]*Bot, *Response) {
query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
r, err := c.DoApiGet(c.GetBotsRoute()+query, etag)
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return BotListFromJson(r.Body), BuildResponse(r)
}
// GetBotsIncludeDeleted fetches the given page of bots, including deleted.
func (c *Client4) GetBotsIncludeDeleted(page, perPage int, etag string) ([]*Bot, *Response) {
query := fmt.Sprintf("?page=%v&per_page=%v&include_deleted=true", page, perPage)
r, err := c.DoApiGet(c.GetBotsRoute()+query, etag)
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return BotListFromJson(r.Body), BuildResponse(r)
}
// GetBotsOrphaned fetches the given page of bots, only including orphanded bots.
func (c *Client4) GetBotsOrphaned(page, perPage int, etag string) ([]*Bot, *Response) {
query := fmt.Sprintf("?page=%v&per_page=%v&only_orphaned=true", page, perPage)
r, err := c.DoApiGet(c.GetBotsRoute()+query, etag)
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return BotListFromJson(r.Body), BuildResponse(r)
}
// DisableBot disables the given bot in the system.
func (c *Client4) DisableBot(botUserId string) (*Bot, *Response) {
r, err := c.doApiPostBytes(c.GetBotRoute(botUserId)+"/disable", nil)
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return BotFromJson(r.Body), BuildResponse(r)
}
// EnableBot disables the given bot in the system.
func (c *Client4) EnableBot(botUserId string) (*Bot, *Response) {
r, err := c.doApiPostBytes(c.GetBotRoute(botUserId)+"/enable", nil)
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return BotFromJson(r.Body), BuildResponse(r)
}
// AssignBot assigns the given bot to the given user
func (c *Client4) AssignBot(botUserId, newOwnerId string) (*Bot, *Response) {
r, err := c.doApiPostBytes(c.GetBotRoute(botUserId)+"/assign/"+newOwnerId, nil)
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return BotFromJson(r.Body), BuildResponse(r)
}
// Team Section
// CreateTeam creates a team in the system based on the provided team struct.

View File

@@ -287,6 +287,7 @@ type ServiceSettings struct {
ExperimentalStrictCSRFEnforcement *bool
EnableEmailInvitations *bool
ExperimentalLdapGroupSync *bool
DisableBotsWhenOwnerIsDeactivated *bool
}
func (s *ServiceSettings) SetDefaults() {
@@ -621,6 +622,10 @@ func (s *ServiceSettings) SetDefaults() {
if s.ExperimentalStrictCSRFEnforcement == nil {
s.ExperimentalStrictCSRFEnforcement = NewBool(false)
}
if s.DisableBotsWhenOwnerIsDeactivated == nil {
s.DisableBotsWhenOwnerIsDeactivated = NewBool(true)
}
}
type ClusterSettings struct {

View File

@@ -69,6 +69,11 @@ var PERMISSION_MANAGE_JOBS *Permission
var PERMISSION_CREATE_USER_ACCESS_TOKEN *Permission
var PERMISSION_READ_USER_ACCESS_TOKEN *Permission
var PERMISSION_REVOKE_USER_ACCESS_TOKEN *Permission
var PERMISSION_CREATE_BOT *Permission
var PERMISSION_READ_BOTS *Permission
var PERMISSION_READ_OTHERS_BOTS *Permission
var PERMISSION_MANAGE_BOTS *Permission
var PERMISSION_MANAGE_OTHERS_BOTS *Permission
// General permission that encompasses all system admin functions
// in the future this could be broken up to allow access to some
@@ -396,6 +401,36 @@ func initializePermissions() {
"authentication.permissions.revoke_user_access_token.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_CREATE_BOT = &Permission{
"create_bot",
"authentication.permissions.create_bot.name",
"authentication.permissions.create_bot.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_READ_BOTS = &Permission{
"read_bots",
"authentication.permissions.read_bots.name",
"authentication.permissions.read_bots.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_READ_OTHERS_BOTS = &Permission{
"read_others_bots",
"authentication.permissions.read_others_bots.name",
"authentication.permissions.read_others_bots.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_MANAGE_BOTS = &Permission{
"manage_bots",
"authentication.permissions.manage_bots.name",
"authentication.permissions.manage_bots.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_MANAGE_OTHERS_BOTS = &Permission{
"manage_others_bots",
"authentication.permissions.manage_others_bots.name",
"authentication.permissions.manage_others_bots.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_MANAGE_JOBS = &Permission{
"manage_jobs",
"authentication.permisssions.manage_jobs.name",
@@ -457,6 +492,11 @@ func initializePermissions() {
PERMISSION_CREATE_USER_ACCESS_TOKEN,
PERMISSION_READ_USER_ACCESS_TOKEN,
PERMISSION_REVOKE_USER_ACCESS_TOKEN,
PERMISSION_CREATE_BOT,
PERMISSION_READ_BOTS,
PERMISSION_READ_OTHERS_BOTS,
PERMISSION_MANAGE_BOTS,
PERMISSION_MANAGE_OTHERS_BOTS,
PERMISSION_MANAGE_SYSTEM,
}
}

View File

@@ -345,6 +345,11 @@ func MakeDefaultRoles() map[string]*Role {
PERMISSION_CREATE_USER_ACCESS_TOKEN.Id,
PERMISSION_READ_USER_ACCESS_TOKEN.Id,
PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id,
PERMISSION_CREATE_BOT.Id,
PERMISSION_READ_BOTS.Id,
PERMISSION_READ_OTHERS_BOTS.Id,
PERMISSION_MANAGE_BOTS.Id,
PERMISSION_MANAGE_OTHERS_BOTS.Id,
PERMISSION_REMOVE_OTHERS_REACTIONS.Id,
},
roles[TEAM_USER_ROLE_ID].Permissions...,

View File

@@ -80,6 +80,7 @@ type User struct {
MfaActive bool `json:"mfa_active,omitempty"`
MfaSecret string `json:"mfa_secret,omitempty"`
LastActivityAt int64 `db:"-" json:"last_activity_at,omitempty"`
IsBot bool `db:"-" json:"is_bot,omitempty"`
}
type UserPatch struct {

16
model/user_count.go Normal file
View File

@@ -0,0 +1,16 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
// Options for counting users
type UserCountOptions struct {
// Should include users that are bots
IncludeBotAccounts bool
// Should include deleted users (of any type)
IncludeDeleted bool
// Exclude regular users
ExcludeRegularUsers bool
// Only include users on a specific team. "" for any team.
TeamId string
}

View File

@@ -508,6 +508,36 @@ type API interface {
//
// Minimum server version: 5.7
SendMail(to, subject, htmlBody string) *model.AppError
// CreateBot creates the given bot and corresponding user.
//
// Minimum server version: 5.10
CreateBot(bot *model.Bot) (*model.Bot, *model.AppError)
// PatchBot applies the given patch to the bot and corresponding user.
//
// Minimum server version: 5.10
PatchBot(botUserId string, botPatch *model.BotPatch) (*model.Bot, *model.AppError)
// GetBot returns the given bot.
//
// Minimum server version: 5.10
GetBot(botUserId string, includeDeleted bool) (*model.Bot, *model.AppError)
// GetBots returns the requested page of bots.
//
// Minimum server version: 5.10
GetBots(options *model.BotGetOptions) ([]*model.Bot, *model.AppError)
// UpdateBotActive marks a bot as active or inactive, along with its corresponding user.
//
// Minimum server version: 5.10
UpdateBotActive(botUserId string, active bool) (*model.Bot, *model.AppError)
// PermanentDeleteBot permanently deletes a bot and its corresponding user.
//
// Minimum server version: 5.10
PermanentDeleteBot(botUserId string) *model.AppError
}
var handshake = plugin.HandshakeConfig{

View File

@@ -3783,3 +3783,179 @@ func (s *apiRPCServer) SendMail(args *Z_SendMailArgs, returns *Z_SendMailReturns
}
return nil
}
type Z_CreateBotArgs struct {
A *model.Bot
}
type Z_CreateBotReturns struct {
A *model.Bot
B *model.AppError
}
func (g *apiRPCClient) CreateBot(bot *model.Bot) (*model.Bot, *model.AppError) {
_args := &Z_CreateBotArgs{bot}
_returns := &Z_CreateBotReturns{}
if err := g.client.Call("Plugin.CreateBot", _args, _returns); err != nil {
log.Printf("RPC call to CreateBot API failed: %s", err.Error())
}
return _returns.A, _returns.B
}
func (s *apiRPCServer) CreateBot(args *Z_CreateBotArgs, returns *Z_CreateBotReturns) error {
if hook, ok := s.impl.(interface {
CreateBot(bot *model.Bot) (*model.Bot, *model.AppError)
}); ok {
returns.A, returns.B = hook.CreateBot(args.A)
} else {
return encodableError(fmt.Errorf("API CreateBot called but not implemented."))
}
return nil
}
type Z_PatchBotArgs struct {
A string
B *model.BotPatch
}
type Z_PatchBotReturns struct {
A *model.Bot
B *model.AppError
}
func (g *apiRPCClient) PatchBot(botUserId string, botPatch *model.BotPatch) (*model.Bot, *model.AppError) {
_args := &Z_PatchBotArgs{botUserId, botPatch}
_returns := &Z_PatchBotReturns{}
if err := g.client.Call("Plugin.PatchBot", _args, _returns); err != nil {
log.Printf("RPC call to PatchBot API failed: %s", err.Error())
}
return _returns.A, _returns.B
}
func (s *apiRPCServer) PatchBot(args *Z_PatchBotArgs, returns *Z_PatchBotReturns) error {
if hook, ok := s.impl.(interface {
PatchBot(botUserId string, botPatch *model.BotPatch) (*model.Bot, *model.AppError)
}); ok {
returns.A, returns.B = hook.PatchBot(args.A, args.B)
} else {
return encodableError(fmt.Errorf("API PatchBot called but not implemented."))
}
return nil
}
type Z_GetBotArgs struct {
A string
B bool
}
type Z_GetBotReturns struct {
A *model.Bot
B *model.AppError
}
func (g *apiRPCClient) GetBot(botUserId string, includeDeleted bool) (*model.Bot, *model.AppError) {
_args := &Z_GetBotArgs{botUserId, includeDeleted}
_returns := &Z_GetBotReturns{}
if err := g.client.Call("Plugin.GetBot", _args, _returns); err != nil {
log.Printf("RPC call to GetBot API failed: %s", err.Error())
}
return _returns.A, _returns.B
}
func (s *apiRPCServer) GetBot(args *Z_GetBotArgs, returns *Z_GetBotReturns) error {
if hook, ok := s.impl.(interface {
GetBot(botUserId string, includeDeleted bool) (*model.Bot, *model.AppError)
}); ok {
returns.A, returns.B = hook.GetBot(args.A, args.B)
} else {
return encodableError(fmt.Errorf("API GetBot called but not implemented."))
}
return nil
}
type Z_GetBotsArgs struct {
A *model.BotGetOptions
}
type Z_GetBotsReturns struct {
A []*model.Bot
B *model.AppError
}
func (g *apiRPCClient) GetBots(options *model.BotGetOptions) ([]*model.Bot, *model.AppError) {
_args := &Z_GetBotsArgs{options}
_returns := &Z_GetBotsReturns{}
if err := g.client.Call("Plugin.GetBots", _args, _returns); err != nil {
log.Printf("RPC call to GetBots API failed: %s", err.Error())
}
return _returns.A, _returns.B
}
func (s *apiRPCServer) GetBots(args *Z_GetBotsArgs, returns *Z_GetBotsReturns) error {
if hook, ok := s.impl.(interface {
GetBots(options *model.BotGetOptions) ([]*model.Bot, *model.AppError)
}); ok {
returns.A, returns.B = hook.GetBots(args.A)
} else {
return encodableError(fmt.Errorf("API GetBots called but not implemented."))
}
return nil
}
type Z_UpdateBotActiveArgs struct {
A string
B bool
}
type Z_UpdateBotActiveReturns struct {
A *model.Bot
B *model.AppError
}
func (g *apiRPCClient) UpdateBotActive(botUserId string, active bool) (*model.Bot, *model.AppError) {
_args := &Z_UpdateBotActiveArgs{botUserId, active}
_returns := &Z_UpdateBotActiveReturns{}
if err := g.client.Call("Plugin.UpdateBotActive", _args, _returns); err != nil {
log.Printf("RPC call to UpdateBotActive API failed: %s", err.Error())
}
return _returns.A, _returns.B
}
func (s *apiRPCServer) UpdateBotActive(args *Z_UpdateBotActiveArgs, returns *Z_UpdateBotActiveReturns) error {
if hook, ok := s.impl.(interface {
UpdateBotActive(botUserId string, active bool) (*model.Bot, *model.AppError)
}); ok {
returns.A, returns.B = hook.UpdateBotActive(args.A, args.B)
} else {
return encodableError(fmt.Errorf("API UpdateBotActive called but not implemented."))
}
return nil
}
type Z_PermanentDeleteBotArgs struct {
A string
}
type Z_PermanentDeleteBotReturns struct {
A *model.AppError
}
func (g *apiRPCClient) PermanentDeleteBot(botUserId string) *model.AppError {
_args := &Z_PermanentDeleteBotArgs{botUserId}
_returns := &Z_PermanentDeleteBotReturns{}
if err := g.client.Call("Plugin.PermanentDeleteBot", _args, _returns); err != nil {
log.Printf("RPC call to PermanentDeleteBot API failed: %s", err.Error())
}
return _returns.A
}
func (s *apiRPCServer) PermanentDeleteBot(args *Z_PermanentDeleteBotArgs, returns *Z_PermanentDeleteBotReturns) error {
if hook, ok := s.impl.(interface {
PermanentDeleteBot(botUserId string) *model.AppError
}); ok {
returns.A = hook.PermanentDeleteBot(args.A)
} else {
return encodableError(fmt.Errorf("API PermanentDeleteBot called but not implemented."))
}
return nil
}

View File

@@ -87,6 +87,31 @@ func (_m *API) CopyFileInfos(userId string, fileIds []string) ([]string, *model.
return r0, r1
}
// CreateBot provides a mock function with given fields: bot
func (_m *API) CreateBot(bot *model.Bot) (*model.Bot, *model.AppError) {
ret := _m.Called(bot)
var r0 *model.Bot
if rf, ok := ret.Get(0).(func(*model.Bot) *model.Bot); ok {
r0 = rf(bot)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Bot)
}
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func(*model.Bot) *model.AppError); ok {
r1 = rf(bot)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// CreateChannel provides a mock function with given fields: channel
func (_m *API) CreateChannel(channel *model.Channel) (*model.Channel, *model.AppError) {
ret := _m.Called(channel)
@@ -365,6 +390,56 @@ func (_m *API) EnablePlugin(id string) *model.AppError {
return r0
}
// GetBot provides a mock function with given fields: botUserId, includeDeleted
func (_m *API) GetBot(botUserId string, includeDeleted bool) (*model.Bot, *model.AppError) {
ret := _m.Called(botUserId, includeDeleted)
var r0 *model.Bot
if rf, ok := ret.Get(0).(func(string, bool) *model.Bot); ok {
r0 = rf(botUserId, includeDeleted)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Bot)
}
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func(string, bool) *model.AppError); ok {
r1 = rf(botUserId, includeDeleted)
} 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)
var r0 []*model.Bot
if rf, ok := ret.Get(0).(func(*model.BotGetOptions) []*model.Bot); ok {
r0 = rf(options)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Bot)
}
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func(*model.BotGetOptions) *model.AppError); ok {
r1 = rf(options)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// GetChannel provides a mock function with given fields: channelId
func (_m *API) GetChannel(channelId string) (*model.Channel, *model.AppError) {
ret := _m.Called(channelId)
@@ -1884,6 +1959,47 @@ func (_m *API) OpenInteractiveDialog(dialog model.OpenDialogRequest) *model.AppE
return r0
}
// PatchBot provides a mock function with given fields: botUserId, botPatch
func (_m *API) PatchBot(botUserId string, botPatch *model.BotPatch) (*model.Bot, *model.AppError) {
ret := _m.Called(botUserId, botPatch)
var r0 *model.Bot
if rf, ok := ret.Get(0).(func(string, *model.BotPatch) *model.Bot); ok {
r0 = rf(botUserId, botPatch)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Bot)
}
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func(string, *model.BotPatch) *model.AppError); ok {
r1 = rf(botUserId, botPatch)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// PermanentDeleteBot provides a mock function with given fields: botUserId
func (_m *API) PermanentDeleteBot(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
}
// PublishWebSocketEvent provides a mock function with given fields: event, payload, broadcast
func (_m *API) PublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *model.WebsocketBroadcast) {
_m.Called(event, payload, broadcast)
@@ -2215,6 +2331,31 @@ func (_m *API) UnregisterCommand(teamId string, trigger string) error {
return r0
}
// UpdateBotActive provides a mock function with given fields: botUserId, active
func (_m *API) UpdateBotActive(botUserId string, active bool) (*model.Bot, *model.AppError) {
ret := _m.Called(botUserId, active)
var r0 *model.Bot
if rf, ok := ret.Get(0).(func(string, bool) *model.Bot); ok {
r0 = rf(botUserId, active)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Bot)
}
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func(string, bool) *model.AppError); ok {
r1 = rf(botUserId, active)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// UpdateChannel provides a mock function with given fields: channel
func (_m *API) UpdateChannel(channel *model.Channel) (*model.Channel, *model.AppError) {
ret := _m.Called(channel)

View File

@@ -87,6 +87,10 @@ func (s *LayeredStore) User() UserStore {
return s.DatabaseLayer.User()
}
func (s *LayeredStore) Bot() BotStore {
return s.DatabaseLayer.Bot()
}
func (s *LayeredStore) Audit() AuditStore {
return s.DatabaseLayer.Audit()
}

253
store/sqlstore/bot_store.go Normal file
View File

@@ -0,0 +1,253 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package sqlstore
import (
"database/sql"
"net/http"
"strings"
"github.com/mattermost/mattermost-server/einterfaces"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
)
// bot is a subset of the model.Bot type, omitting the model.User fields.
type bot struct {
UserId string `json:"user_id"`
Description string `json:"description"`
OwnerId string `json:"owner_id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
}
func botFromModel(b *model.Bot) *bot {
return &bot{
UserId: b.UserId,
Description: b.Description,
OwnerId: b.OwnerId,
CreateAt: b.CreateAt,
UpdateAt: b.UpdateAt,
DeleteAt: b.DeleteAt,
}
}
// SqlBotStore is a store for managing bots in the database.
// Bots are otherwise normal users with extra metadata record in the Bots table. The primary key
// for a bot matches the primary key value for corresponding User record.
type SqlBotStore struct {
SqlStore
metrics einterfaces.MetricsInterface
}
// NewSqlBotStore creates an instance of SqlBotStore, registering the table schema in question.
func NewSqlBotStore(sqlStore SqlStore, metrics einterfaces.MetricsInterface) store.BotStore {
us := &SqlBotStore{
SqlStore: sqlStore,
metrics: metrics,
}
for _, db := range sqlStore.GetAllConns() {
table := db.AddTableWithName(bot{}, "Bots").SetKeys(false, "UserId")
table.ColMap("UserId").SetMaxSize(26)
table.ColMap("Description").SetMaxSize(1024)
table.ColMap("OwnerId").SetMaxSize(model.BOT_CREATOR_ID_MAX_RUNES)
}
return us
}
func (us SqlBotStore) CreateIndexesIfNotExists() {
}
// traceBot is a helper function for adding to a bot trace when logging.
func traceBot(bot *model.Bot, extra map[string]interface{}) map[string]interface{} {
trace := make(map[string]interface{})
for key, value := range bot.Trace() {
trace[key] = value
}
for key, value := range extra {
trace[key] = value
}
return trace
}
// Get fetches the given bot in the database.
func (us SqlBotStore) Get(botUserId string, includeDeleted bool) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
var excludeDeletedSql = "AND b.DeleteAt = 0"
if includeDeleted {
excludeDeletedSql = ""
}
var bot *model.Bot
if err := us.GetReplica().SelectOne(&bot, `
SELECT
b.UserId,
u.Username,
u.FirstName AS DisplayName,
b.Description,
b.OwnerId,
b.CreateAt,
b.UpdateAt,
b.DeleteAt
FROM
Bots b
JOIN
Users u ON (u.Id = b.UserId)
WHERE
b.UserId = :user_id
`+excludeDeletedSql+`
`, map[string]interface{}{
"user_id": botUserId,
}); err == sql.ErrNoRows {
result.Err = model.MakeBotNotFoundError(botUserId)
} else if err != nil {
result.Err = model.NewAppError("SqlBotStore.Get", "store.sql_bot.get.app_error", map[string]interface{}{"user_id": botUserId}, err.Error(), http.StatusInternalServerError)
} else {
result.Data = bot
}
})
}
// GetAll fetches from all bots in the database.
func (us SqlBotStore) GetAll(options *model.BotGetOptions) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
params := map[string]interface{}{
"offset": options.Page * options.PerPage,
"limit": options.PerPage,
}
var conditions []string
var conditionsSql string
var additionalJoin string
if !options.IncludeDeleted {
conditions = append(conditions, "b.DeleteAt = 0")
}
if options.OwnerId != "" {
conditions = append(conditions, "b.OwnerId = :creator_id")
params["creator_id"] = options.OwnerId
}
if options.OnlyOrphaned {
additionalJoin = "JOIN Users o ON (o.Id = b.OwnerId)"
conditions = append(conditions, "o.DeleteAt != 0")
}
if len(conditions) > 0 {
conditionsSql = "WHERE " + strings.Join(conditions, " AND ")
}
sql := `
SELECT
b.UserId,
u.Username,
u.FirstName AS DisplayName,
b.Description,
b.OwnerId,
b.CreateAt,
b.UpdateAt,
b.DeleteAt
FROM
Bots b
JOIN
Users u ON (u.Id = b.UserId)
` + additionalJoin + `
` + conditionsSql + `
ORDER BY
b.CreateAt ASC,
u.Username ASC
LIMIT
:limit
OFFSET
:offset
`
var data []*model.Bot
if _, err := us.GetReplica().Select(&data, sql, params); err != nil {
result.Err = model.NewAppError("SqlBotStore.GetAll", "store.sql_bot.get_all.app_error", nil, err.Error(), http.StatusInternalServerError)
}
result.Data = data
})
}
// Save persists a new bot to the database.
// It assumes the corresponding user was saved via the user store.
func (us SqlBotStore) Save(bot *model.Bot) store.StoreChannel {
bot = bot.Clone()
return store.Do(func(result *store.StoreResult) {
bot.PreSave()
if result.Err = bot.IsValid(); result.Err != nil {
return
}
if err := us.GetMaster().Insert(botFromModel(bot)); err != nil {
result.Err = model.NewAppError("SqlBotStore.Save", "store.sql_bot.save.app_error", bot.Trace(), err.Error(), http.StatusInternalServerError)
return
}
result.Data = bot
})
}
// Update persists an updated bot to the database.
// It assumes the corresponding user was updated via the user store.
func (us SqlBotStore) Update(bot *model.Bot) store.StoreChannel {
bot = bot.Clone()
return store.Do(func(result *store.StoreResult) {
bot.PreUpdate()
if result.Err = bot.IsValid(); result.Err != nil {
return
}
oldBotResult := <-us.Get(bot.UserId, true)
if oldBotResult.Err != nil {
result.Err = oldBotResult.Err
return
}
oldBot := oldBotResult.Data.(*model.Bot)
oldBot.Description = bot.Description
oldBot.OwnerId = bot.OwnerId
oldBot.UpdateAt = bot.UpdateAt
oldBot.DeleteAt = bot.DeleteAt
bot = oldBot
if count, err := us.GetMaster().Update(botFromModel(bot)); err != nil {
result.Err = model.NewAppError("SqlBotStore.Update", "store.sql_bot.update.updating.app_error", bot.Trace(), err.Error(), http.StatusInternalServerError)
} else if count != 1 {
result.Err = model.NewAppError("SqlBotStore.Update", "store.sql_bot.update.app_error", traceBot(bot, map[string]interface{}{"count": count}), "", http.StatusInternalServerError)
}
result.Data = bot
})
}
// PermanentDelete removes the bot from the database altogether.
// If the corresponding user is to be deleted, it must be done via the user store.
func (us SqlBotStore) PermanentDelete(botUserId string) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
userResult := <-us.User().PermanentDelete(botUserId)
if userResult.Err != nil {
result.Err = userResult.Err
return
}
if _, err := us.GetMaster().Exec(`
DELETE FROM
Bots
WHERE
UserId = :user_id
`, map[string]interface{}{
"user_id": botUserId,
}); err != nil {
result.Err = model.NewAppError("SqlBotStore.Update", "store.sql_bot.delete.app_error", map[string]interface{}{"user_id": botUserId}, err.Error(), http.StatusBadRequest)
}
})
}

View File

@@ -0,0 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package sqlstore
import (
"testing"
"github.com/mattermost/mattermost-server/store/storetest"
)
func TestBotStore(t *testing.T) {
StoreTest(t, storetest.TestBotStore)
}

View File

@@ -73,6 +73,7 @@ type SqlStore interface {
Channel() store.ChannelStore
Post() store.PostStore
User() store.UserStore
Bot() store.BotStore
Audit() store.AuditStore
ClusterDiscovery() store.ClusterDiscoveryStore
Compliance() store.ComplianceStore

View File

@@ -71,6 +71,7 @@ type SqlSupplierOldStores struct {
channel store.ChannelStore
post store.PostStore
user store.UserStore
bot store.BotStore
audit store.AuditStore
cluster store.ClusterDiscoveryStore
compliance store.ComplianceStore
@@ -126,6 +127,7 @@ func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInter
supplier.oldStores.channel = NewSqlChannelStore(supplier, metrics)
supplier.oldStores.post = NewSqlPostStore(supplier, metrics)
supplier.oldStores.user = NewSqlUserStore(supplier, metrics)
supplier.oldStores.bot = NewSqlBotStore(supplier, metrics)
supplier.oldStores.audit = NewSqlAuditStore(supplier)
supplier.oldStores.cluster = NewSqlClusterDiscoveryStore(supplier)
supplier.oldStores.compliance = NewSqlComplianceStore(supplier)
@@ -167,6 +169,7 @@ func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInter
supplier.oldStores.channel.(*SqlChannelStore).CreateIndexesIfNotExists()
supplier.oldStores.post.(*SqlPostStore).CreateIndexesIfNotExists()
supplier.oldStores.user.(*SqlUserStore).CreateIndexesIfNotExists()
supplier.oldStores.bot.(*SqlBotStore).CreateIndexesIfNotExists()
supplier.oldStores.audit.(*SqlAuditStore).CreateIndexesIfNotExists()
supplier.oldStores.compliance.(*SqlComplianceStore).CreateIndexesIfNotExists()
supplier.oldStores.session.(*SqlSessionStore).CreateIndexesIfNotExists()
@@ -936,6 +939,10 @@ func (ss *SqlSupplier) User() store.UserStore {
return ss.oldStores.user
}
func (ss *SqlSupplier) Bot() store.BotStore {
return ss.oldStores.bot
}
func (ss *SqlSupplier) Session() store.SessionStore {
return ss.oldStores.session
}

View File

@@ -4,12 +4,15 @@
package sqlstore
import (
"database/sql"
"encoding/json"
"fmt"
"os"
"strings"
"time"
"github.com/pkg/errors"
"github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/services/timezones"
@@ -55,10 +58,11 @@ const (
)
const (
EXIT_VERSION_SAVE_MISSING = 1001
EXIT_TOO_OLD = 1002
EXIT_VERSION_SAVE = 1003
EXIT_THEME_MIGRATION = 1004
EXIT_VERSION_SAVE_MISSING = 1001
EXIT_TOO_OLD = 1002
EXIT_VERSION_SAVE = 1003
EXIT_THEME_MIGRATION = 1004
EXIT_ROLE_MIGRATION_FAILED = 1005
)
func UpgradeDatabase(sqlStore SqlStore) {
@@ -554,6 +558,33 @@ func UpgradeDatabaseToVersion57(sqlStore SqlStore) {
}
}
func getRole(sqlStore SqlStore, name string) (*model.Role, error) {
var dbRole Role
if err := sqlStore.GetReplica().SelectOne(&dbRole, "SELECT * from Roles WHERE Name = :Name", map[string]interface{}{"Name": name}); err != nil {
if err == sql.ErrNoRows {
return nil, errors.Wrapf(err, "failed to find role %s", name)
} else {
return nil, errors.Wrapf(err, "failed to query role %s", name)
}
}
return dbRole.ToModel(), nil
}
func saveRole(sqlStore SqlStore, role *model.Role) error {
dbRole := NewRoleFromModel(role)
dbRole.UpdateAt = model.GetMillis()
if rowsChanged, err := sqlStore.GetMaster().Update(dbRole); err != nil {
return errors.Wrap(err, "failed to update role")
} else if rowsChanged != 1 {
return errors.New("found no role to update")
}
return nil
}
func UpgradeDatabaseToVersion58(sqlStore SqlStore) {
if shouldPerformUpgrade(sqlStore, VERSION_5_7_0, VERSION_5_8_0) {
// idx_channels_txt was removed in `UpgradeDatabaseToVersion50`, but merged as part of
@@ -583,6 +614,26 @@ func UpgradeDatabaseToVersion59(sqlStore SqlStore) {
func UpgradeDatabaseToVersion510(sqlStore SqlStore) {
// if shouldPerformUpgrade(sqlStore, VERSION_5_9_0, VERSION_5_10_0) {
// Grant new bot permissions to the system admin. Ideally we'd use the RoleStore directly,
// but it uses the new supplier model, which isn't initialized in the UpgradeDatabase code
// path. Also, the role won't exist for new servers, so don't fail on fetch, and don't
// bother inserting since it will be created with the new permissions anyway.
if role, err := getRole(sqlStore, model.SYSTEM_ADMIN_ROLE_ID); err != nil {
mlog.Warn("Failed to find role " + model.SYSTEM_ADMIN_ROLE_ID + " for upgrade: " + err.Error())
} else {
role.Permissions = append(role.Permissions, model.PERMISSION_CREATE_BOT.Id)
role.Permissions = append(role.Permissions, model.PERMISSION_READ_BOTS.Id)
role.Permissions = append(role.Permissions, model.PERMISSION_READ_OTHERS_BOTS.Id)
role.Permissions = append(role.Permissions, model.PERMISSION_MANAGE_BOTS.Id)
role.Permissions = append(role.Permissions, model.PERMISSION_MANAGE_OTHERS_BOTS.Id)
if err := saveRole(sqlStore, role); err != nil {
mlog.Critical(err.Error())
time.Sleep(time.Second)
os.Exit(EXIT_ROLE_MIGRATION_FAILED)
}
}
// saveSchemaVersion(sqlStore, VERSION_5_10_0)
// }
}

View File

@@ -68,8 +68,9 @@ func NewSqlUserStore(sqlStore SqlStore, metrics einterfaces.MetricsInterface) st
}
us.usersQuery = sq.
Select("u.*").
From("Users u")
Select("u.*", "b.UserId IS NOT NULL AS IsBot").
From("Users u").
LeftJoin("Bots b ON ( b.UserId = u.Id )")
if us.DriverName() == model.DATABASE_DRIVER_POSTGRES {
us.usersQuery = us.usersQuery.PlaceholderFormat(sq.Dollar)
@@ -1036,16 +1037,6 @@ func (us SqlUserStore) VerifyEmail(userId, email string) store.StoreChannel {
})
}
func (us SqlUserStore) GetTotalUsersCount() store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
if count, err := us.GetReplica().SelectInt("SELECT COUNT(Id) FROM Users"); err != nil {
result.Err = model.NewAppError("SqlUserStore.GetTotalUsersCount", "store.sql_user.get_total_users_count.app_error", nil, err.Error(), http.StatusInternalServerError)
} else {
result.Data = count
}
})
}
func (us SqlUserStore) PermanentDelete(userId string) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
if _, err := us.GetMaster().Exec("DELETE FROM Users WHERE Id = :UserId", map[string]interface{}{"UserId": userId}); err != nil {
@@ -1054,20 +1045,45 @@ func (us SqlUserStore) PermanentDelete(userId string) store.StoreChannel {
})
}
func (us SqlUserStore) AnalyticsUniqueUserCount(teamId string) store.StoreChannel {
func (us SqlUserStore) Count(options model.UserCountOptions) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
query := ""
if len(teamId) > 0 {
query = "SELECT COUNT(DISTINCT Users.Email) From Users, TeamMembers WHERE TeamMembers.TeamId = :TeamId AND Users.Id = TeamMembers.UserId AND TeamMembers.DeleteAt = 0 AND Users.DeleteAt = 0"
} else {
query = "SELECT COUNT(DISTINCT Email) FROM Users WHERE DeleteAt = 0"
query := sq.Select("COUNT(Users.Id)").From("Users")
if !options.IncludeDeleted {
query = query.Where("Users.DeleteAt = 0")
}
v, err := us.GetReplica().SelectInt(query, map[string]interface{}{"TeamId": teamId})
if err != nil {
result.Err = model.NewAppError("SqlUserStore.AnalyticsUniqueUserCount", "store.sql_user.analytics_unique_user_count.app_error", nil, err.Error(), http.StatusInternalServerError)
if options.IncludeBotAccounts {
if options.ExcludeRegularUsers {
query = query.Join("Bots ON Users.Id = Bots.UserId")
}
} else {
result.Data = v
query = query.LeftJoin("Bots ON Users.Id = Bots.UserId").Where("Bots.UserId IS NULL")
if options.ExcludeRegularUsers {
// Currenty this doesn't make sense because it will always return 0
result.Err = model.NewAppError("SqlUserStore.Count", "UserCountOptions don't make sense", nil, "", http.StatusInternalServerError)
return
}
}
if options.TeamId != "" {
query = query.LeftJoin("TeamMembers ON Users.Id = TeamMembers.UserId").Where("TeamMembers.TeamId = ? AND TeamMembers.DeleteAt = 0", options.TeamId)
}
if us.DriverName() == model.DATABASE_DRIVER_POSTGRES {
query = query.PlaceholderFormat(sq.Dollar)
}
queryString, args, err := query.ToSql()
if err != nil {
result.Err = model.NewAppError("SqlUserStore.Get", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
if count, err := us.GetReplica().SelectInt(queryString, args...); err != nil {
result.Err = model.NewAppError("SqlUserStore.Count", "store.sql_user.get_total_users_count.app_error", nil, err.Error(), http.StatusInternalServerError)
} else {
result.Data = count
}
})
}

View File

@@ -43,6 +43,7 @@ type Store interface {
Channel() ChannelStore
Post() PostStore
User() UserStore
Bot() BotStore
Audit() AuditStore
ClusterDiscovery() ClusterDiscoveryStore
Compliance() ComplianceStore
@@ -264,10 +265,8 @@ type UserStore interface {
GetEtagForAllProfiles() StoreChannel
GetEtagForProfiles(teamId string) StoreChannel
UpdateFailedPasswordAttempts(userId string, attempts int) StoreChannel
GetTotalUsersCount() StoreChannel
GetSystemAdminProfiles() StoreChannel
PermanentDelete(userId string) StoreChannel
AnalyticsUniqueUserCount(teamId string) StoreChannel
AnalyticsActiveCount(time int64) StoreChannel
GetUnreadCount(userId string) StoreChannel
GetUnreadCountForChannel(userId string, channelId string) StoreChannel
@@ -286,6 +285,15 @@ type UserStore interface {
ClearAllCustomRoleAssignments() StoreChannel
InferSystemInstallDate() StoreChannel
GetAllAfter(limit int, afterId string) StoreChannel
Count(options model.UserCountOptions) StoreChannel
}
type BotStore interface {
Get(userId string, includeDeleted bool) StoreChannel
GetAll(options *model.BotGetOptions) StoreChannel
Save(bot *model.Bot) StoreChannel
Update(bot *model.Bot) StoreChannel
PermanentDelete(userId string) StoreChannel
}
type SessionStore interface {

View File

@@ -0,0 +1,441 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package storetest
import (
"net/http"
"testing"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
)
func makeBotWithUser(ss store.Store, bot *model.Bot) (*model.Bot, *model.User) {
user := store.Must(ss.User().Save(model.UserFromBot(bot))).(*model.User)
bot.UserId = user.Id
bot = store.Must(ss.Bot().Save(bot)).(*model.Bot)
return bot, user
}
func TestBotStore(t *testing.T, ss store.Store) {
t.Run("Get", func(t *testing.T) { testBotStoreGet(t, ss) })
t.Run("GetAll", func(t *testing.T) { testBotStoreGetAll(t, ss) })
t.Run("Save", func(t *testing.T) { testBotStoreSave(t, ss) })
t.Run("Update", func(t *testing.T) { testBotStoreUpdate(t, ss) })
t.Run("PermanentDelete", func(t *testing.T) { testBotStorePermanentDelete(t, ss) })
}
func testBotStoreGet(t *testing.T, ss store.Store) {
deletedBot, _ := makeBotWithUser(ss, &model.Bot{
Username: "deleted_bot",
Description: "A deleted bot",
OwnerId: model.NewId(),
})
deletedBot.DeleteAt = 1
deletedBot = store.Must(ss.Bot().Update(deletedBot)).(*model.Bot)
defer func() { store.Must(ss.Bot().PermanentDelete(deletedBot.UserId)) }()
defer func() { store.Must(ss.User().PermanentDelete(deletedBot.UserId)) }()
permanentlyDeletedBot, _ := makeBotWithUser(ss, &model.Bot{
Username: "permanently_deleted_bot",
Description: "A permanently deleted bot",
OwnerId: model.NewId(),
DeleteAt: 0,
})
store.Must(ss.Bot().PermanentDelete(permanentlyDeletedBot.UserId))
b1, _ := makeBotWithUser(ss, &model.Bot{
Username: "b1",
Description: "The first bot",
OwnerId: model.NewId(),
})
defer func() { store.Must(ss.Bot().PermanentDelete(b1.UserId)) }()
defer func() { store.Must(ss.User().PermanentDelete(b1.UserId)) }()
b2, _ := makeBotWithUser(ss, &model.Bot{
Username: "b2",
Description: "The second bot",
OwnerId: model.NewId(),
})
defer func() { store.Must(ss.Bot().PermanentDelete(b2.UserId)) }()
defer func() { store.Must(ss.User().PermanentDelete(b2.UserId)) }()
t.Run("get non-existent bot", func(t *testing.T) {
result := <-ss.Bot().Get("unknown", false)
require.NotNil(t, result.Err)
require.Equal(t, http.StatusNotFound, result.Err.StatusCode)
})
t.Run("get deleted bot", func(t *testing.T) {
result := <-ss.Bot().Get(deletedBot.UserId, false)
require.NotNil(t, result.Err)
require.Equal(t, http.StatusNotFound, result.Err.StatusCode)
})
t.Run("get deleted bot, include deleted", func(t *testing.T) {
result := <-ss.Bot().Get(deletedBot.UserId, true)
require.Nil(t, result.Err)
require.Equal(t, deletedBot, result.Data.(*model.Bot))
})
t.Run("get permanently deleted bot", func(t *testing.T) {
result := <-ss.Bot().Get(permanentlyDeletedBot.UserId, false)
require.NotNil(t, result.Err)
require.Equal(t, http.StatusNotFound, result.Err.StatusCode)
})
t.Run("get bot 1", func(t *testing.T) {
result := <-ss.Bot().Get(b1.UserId, false)
require.Nil(t, result.Err)
require.Equal(t, b1, result.Data.(*model.Bot))
})
t.Run("get bot 2", func(t *testing.T) {
result := <-ss.Bot().Get(b2.UserId, false)
require.Nil(t, result.Err)
require.Equal(t, b2, result.Data.(*model.Bot))
})
}
func testBotStoreGetAll(t *testing.T, ss store.Store) {
OwnerId1 := model.NewId()
OwnerId2 := model.NewId()
deletedBot, _ := makeBotWithUser(ss, &model.Bot{
Username: "deleted_bot",
Description: "A deleted bot",
OwnerId: OwnerId1,
})
deletedBot.DeleteAt = 1
deletedBot = store.Must(ss.Bot().Update(deletedBot)).(*model.Bot)
defer func() { store.Must(ss.Bot().PermanentDelete(deletedBot.UserId)) }()
defer func() { store.Must(ss.User().PermanentDelete(deletedBot.UserId)) }()
permanentlyDeletedBot, _ := makeBotWithUser(ss, &model.Bot{
Username: "permanently_deleted_bot",
Description: "A permanently deleted bot",
OwnerId: OwnerId1,
DeleteAt: 0,
})
store.Must(ss.Bot().PermanentDelete(permanentlyDeletedBot.UserId))
b1, _ := makeBotWithUser(ss, &model.Bot{
Username: "b1",
Description: "The first bot",
OwnerId: OwnerId1,
})
defer func() { store.Must(ss.Bot().PermanentDelete(b1.UserId)) }()
defer func() { store.Must(ss.User().PermanentDelete(b1.UserId)) }()
b2, _ := makeBotWithUser(ss, &model.Bot{
Username: "b2",
Description: "The second bot",
OwnerId: OwnerId1,
})
defer func() { store.Must(ss.Bot().PermanentDelete(b2.UserId)) }()
defer func() { store.Must(ss.User().PermanentDelete(b2.UserId)) }()
t.Run("get original bots", func(t *testing.T) {
result := <-ss.Bot().GetAll(&model.BotGetOptions{Page: 0, PerPage: 10})
require.Nil(t, result.Err)
require.Equal(t, []*model.Bot{
b1,
b2,
}, result.Data.([]*model.Bot))
})
b3, _ := makeBotWithUser(ss, &model.Bot{
Username: "b3",
Description: "The third bot",
OwnerId: OwnerId1,
})
defer func() { store.Must(ss.Bot().PermanentDelete(b3.UserId)) }()
defer func() { store.Must(ss.User().PermanentDelete(b3.UserId)) }()
b4, _ := makeBotWithUser(ss, &model.Bot{
Username: "b4",
Description: "The fourth bot",
OwnerId: OwnerId2,
})
defer func() { store.Must(ss.Bot().PermanentDelete(b4.UserId)) }()
defer func() { store.Must(ss.User().PermanentDelete(b4.UserId)) }()
deletedUser := model.User{
Email: MakeEmail(),
Username: model.NewId(),
}
if err := (<-ss.User().Save(&deletedUser)).Err; err != nil {
t.Fatal("couldn't save user", err)
}
deletedUser.DeleteAt = model.GetMillis()
if err := (<-ss.User().Update(&deletedUser, true)).Err; err != nil {
t.Fatal("couldn't delete user", err)
}
defer func() { store.Must(ss.User().PermanentDelete(deletedUser.Id)) }()
ob5, _ := makeBotWithUser(ss, &model.Bot{
Username: "ob5",
Description: "Orphaned bot 5",
OwnerId: deletedUser.Id,
})
defer func() { store.Must(ss.Bot().PermanentDelete(b4.UserId)) }()
defer func() { store.Must(ss.User().PermanentDelete(b4.UserId)) }()
t.Run("get newly created bot stoo", func(t *testing.T) {
result := <-ss.Bot().GetAll(&model.BotGetOptions{Page: 0, PerPage: 10})
require.Nil(t, result.Err)
require.Equal(t, []*model.Bot{
b1,
b2,
b3,
b4,
ob5,
}, result.Data.([]*model.Bot))
})
t.Run("get orphaned", func(t *testing.T) {
result := <-ss.Bot().GetAll(&model.BotGetOptions{Page: 0, PerPage: 10, OnlyOrphaned: true})
require.Nil(t, result.Err)
require.Equal(t, []*model.Bot{
ob5,
}, result.Data.([]*model.Bot))
})
t.Run("get page=0, per_page=2", func(t *testing.T) {
result := <-ss.Bot().GetAll(&model.BotGetOptions{Page: 0, PerPage: 2})
require.Nil(t, result.Err)
require.Equal(t, []*model.Bot{
b1,
b2,
}, result.Data.([]*model.Bot))
})
t.Run("get page=1, limit=2", func(t *testing.T) {
result := <-ss.Bot().GetAll(&model.BotGetOptions{Page: 1, PerPage: 2})
require.Nil(t, result.Err)
require.Equal(t, []*model.Bot{
b3,
b4,
}, result.Data.([]*model.Bot))
})
t.Run("get page=5, perpage=1000", func(t *testing.T) {
result := <-ss.Bot().GetAll(&model.BotGetOptions{Page: 5, PerPage: 1000})
require.Nil(t, result.Err)
require.Equal(t, []*model.Bot{}, result.Data.([]*model.Bot))
})
t.Run("get offset=0, limit=2, include deleted", func(t *testing.T) {
result := <-ss.Bot().GetAll(&model.BotGetOptions{Page: 0, PerPage: 2, IncludeDeleted: true})
require.Nil(t, result.Err)
require.Equal(t, []*model.Bot{
deletedBot,
b1,
}, result.Data.([]*model.Bot))
})
t.Run("get offset=2, limit=2, include deleted", func(t *testing.T) {
result := <-ss.Bot().GetAll(&model.BotGetOptions{Page: 1, PerPage: 2, IncludeDeleted: true})
require.Nil(t, result.Err)
require.Equal(t, []*model.Bot{
b2,
b3,
}, result.Data.([]*model.Bot))
})
t.Run("get offset=0, limit=10, creator id 1", func(t *testing.T) {
result := <-ss.Bot().GetAll(&model.BotGetOptions{Page: 0, PerPage: 10, OwnerId: OwnerId1})
require.Nil(t, result.Err)
require.Equal(t, []*model.Bot{
b1,
b2,
b3,
}, result.Data.([]*model.Bot))
})
t.Run("get offset=0, limit=10, creator id 2", func(t *testing.T) {
result := <-ss.Bot().GetAll(&model.BotGetOptions{Page: 0, PerPage: 10, OwnerId: OwnerId2})
require.Nil(t, result.Err)
require.Equal(t, []*model.Bot{
b4,
}, result.Data.([]*model.Bot))
})
t.Run("get offset=0, limit=10, include deleted, creator id 1", func(t *testing.T) {
result := <-ss.Bot().GetAll(&model.BotGetOptions{Page: 0, PerPage: 10, IncludeDeleted: true, OwnerId: OwnerId1})
require.Nil(t, result.Err)
require.Equal(t, []*model.Bot{
deletedBot,
b1,
b2,
b3,
}, result.Data.([]*model.Bot))
})
t.Run("get offset=0, limit=10, include deleted, creator id 2", func(t *testing.T) {
result := <-ss.Bot().GetAll(&model.BotGetOptions{Page: 0, PerPage: 10, IncludeDeleted: true, OwnerId: OwnerId2})
require.Nil(t, result.Err)
require.Equal(t, []*model.Bot{
b4,
}, result.Data.([]*model.Bot))
})
}
func testBotStoreSave(t *testing.T, ss store.Store) {
t.Run("invalid bot", func(t *testing.T) {
bot := &model.Bot{
UserId: model.NewId(),
Username: "invalid bot",
Description: "description",
}
result := <-ss.Bot().Save(bot)
require.NotNil(t, result.Err)
require.Equal(t, "model.bot.is_valid.username.app_error", result.Err.Id)
})
t.Run("normal bot", func(t *testing.T) {
bot := &model.Bot{
Username: "normal_bot",
Description: "description",
OwnerId: model.NewId(),
}
user := store.Must(ss.User().Save(model.UserFromBot(bot))).(*model.User)
defer func() { store.Must(ss.User().PermanentDelete(user.Id)) }()
bot.UserId = user.Id
result := <-ss.Bot().Save(bot)
require.Nil(t, result.Err)
defer func() { store.Must(ss.Bot().PermanentDelete(bot.UserId)) }()
// Verify the returned bot matches the saved bot, modulo expected changes
returnedNewBot := result.Data.(*model.Bot)
require.NotEqual(t, 0, returnedNewBot.CreateAt)
require.NotEqual(t, 0, returnedNewBot.UpdateAt)
require.Equal(t, returnedNewBot.CreateAt, returnedNewBot.UpdateAt)
bot.UserId = returnedNewBot.UserId
bot.CreateAt = returnedNewBot.CreateAt
bot.UpdateAt = returnedNewBot.UpdateAt
bot.DeleteAt = 0
require.Equal(t, bot, returnedNewBot)
// Verify the actual bot in the database matches the saved bot.
result = <-ss.Bot().Get(bot.UserId, false)
require.Nil(t, result.Err)
actualNewBot := result.Data.(*model.Bot)
require.Equal(t, bot, actualNewBot)
})
}
func testBotStoreUpdate(t *testing.T, ss store.Store) {
t.Run("invalid bot should fail to update", func(t *testing.T) {
existingBot, _ := makeBotWithUser(ss, &model.Bot{
Username: "existing_bot",
OwnerId: model.NewId(),
})
defer func() { store.Must(ss.Bot().PermanentDelete(existingBot.UserId)) }()
defer func() { store.Must(ss.User().PermanentDelete(existingBot.UserId)) }()
bot := existingBot.Clone()
bot.Username = "invalid username"
result := <-ss.Bot().Update(bot)
require.NotNil(t, result.Err)
require.Equal(t, "model.bot.is_valid.username.app_error", result.Err.Id)
})
t.Run("existing bot should update", func(t *testing.T) {
existingBot, _ := makeBotWithUser(ss, &model.Bot{
Username: "existing_bot",
OwnerId: model.NewId(),
})
defer func() { store.Must(ss.Bot().PermanentDelete(existingBot.UserId)) }()
defer func() { store.Must(ss.User().PermanentDelete(existingBot.UserId)) }()
bot := existingBot.Clone()
bot.OwnerId = model.NewId()
bot.Description = "updated description"
bot.CreateAt = 999999 // Ignored
bot.UpdateAt = 999999 // Ignored
bot.DeleteAt = 100000 // Allowed
result := <-ss.Bot().Update(bot)
require.Nil(t, result.Err)
// Verify the returned bot matches the updated bot, modulo expected timestamp changes
returnedBot := result.Data.(*model.Bot)
require.Equal(t, existingBot.CreateAt, returnedBot.CreateAt)
require.NotEqual(t, bot.UpdateAt, returnedBot.UpdateAt, "update should have advanced UpdateAt")
require.True(t, returnedBot.UpdateAt > bot.UpdateAt, "update should have advanced UpdateAt")
require.NotEqual(t, 99999, returnedBot.UpdateAt, "should have ignored user-provided UpdateAt")
bot.CreateAt = returnedBot.CreateAt
bot.UpdateAt = returnedBot.UpdateAt
// Verify the actual (now deleted) bot in the database
result = <-ss.Bot().Get(bot.UserId, true)
require.Nil(t, result.Err)
require.Equal(t, bot, result.Data.(*model.Bot))
})
t.Run("deleted bot should update, restoring", func(t *testing.T) {
existingBot, _ := makeBotWithUser(ss, &model.Bot{
Username: "existing_bot",
OwnerId: model.NewId(),
})
defer func() { store.Must(ss.Bot().PermanentDelete(existingBot.UserId)) }()
defer func() { store.Must(ss.User().PermanentDelete(existingBot.UserId)) }()
existingBot.DeleteAt = 100000
existingBot = store.Must(ss.Bot().Update(existingBot)).(*model.Bot)
bot := existingBot.Clone()
bot.DeleteAt = 0
result := <-ss.Bot().Update(bot)
require.Nil(t, result.Err)
// Verify the returned bot matches the updated bot, modulo expected timestamp changes
returnedBot := result.Data.(*model.Bot)
require.EqualValues(t, 0, returnedBot.DeleteAt)
bot.UpdateAt = returnedBot.UpdateAt
// Verify the actual bot in the database
result = <-ss.Bot().Get(bot.UserId, false)
require.Nil(t, result.Err)
require.Equal(t, bot, result.Data.(*model.Bot))
})
}
func testBotStorePermanentDelete(t *testing.T, ss store.Store) {
b1, _ := makeBotWithUser(ss, &model.Bot{
Username: "b1",
OwnerId: model.NewId(),
})
defer func() { store.Must(ss.Bot().PermanentDelete(b1.UserId)) }()
defer func() { store.Must(ss.User().PermanentDelete(b1.UserId)) }()
b2, _ := makeBotWithUser(ss, &model.Bot{
Username: "b2",
OwnerId: model.NewId(),
})
defer func() { store.Must(ss.Bot().PermanentDelete(b2.UserId)) }()
defer func() { store.Must(ss.User().PermanentDelete(b2.UserId)) }()
t.Run("permanently delete a non-existent bot", func(t *testing.T) {
result := <-ss.Bot().PermanentDelete("unknown")
require.Nil(t, result.Err)
})
t.Run("permanently delete bot", func(t *testing.T) {
result := <-ss.Bot().PermanentDelete(b1.UserId)
require.Nil(t, result.Err)
result = <-ss.Bot().Get(b1.UserId, false)
require.NotNil(t, result.Err)
require.Equal(t, http.StatusNotFound, result.Err.StatusCode)
})
}

View File

@@ -0,0 +1,94 @@
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`.
package mocks
import mock "github.com/stretchr/testify/mock"
import model "github.com/mattermost/mattermost-server/model"
import store "github.com/mattermost/mattermost-server/store"
// BotStore is an autogenerated mock type for the BotStore type
type BotStore struct {
mock.Mock
}
// Get provides a mock function with given fields: userId, includeDeleted
func (_m *BotStore) Get(userId string, includeDeleted bool) store.StoreChannel {
ret := _m.Called(userId, includeDeleted)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string, bool) store.StoreChannel); ok {
r0 = rf(userId, includeDeleted)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// GetAll provides a mock function with given fields: options
func (_m *BotStore) GetAll(options *model.BotGetOptions) store.StoreChannel {
ret := _m.Called(options)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(*model.BotGetOptions) store.StoreChannel); ok {
r0 = rf(options)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// PermanentDelete provides a mock function with given fields: userId
func (_m *BotStore) PermanentDelete(userId string) store.StoreChannel {
ret := _m.Called(userId)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok {
r0 = rf(userId)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// Save provides a mock function with given fields: bot
func (_m *BotStore) Save(bot *model.Bot) store.StoreChannel {
ret := _m.Called(bot)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(*model.Bot) store.StoreChannel); ok {
r0 = rf(bot)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// Update provides a mock function with given fields: bot
func (_m *BotStore) Update(bot *model.Bot) store.StoreChannel {
ret := _m.Called(bot)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(*model.Bot) store.StoreChannel); ok {
r0 = rf(bot)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}

View File

@@ -30,6 +30,22 @@ func (_m *LayeredStoreDatabaseLayer) Audit() store.AuditStore {
return r0
}
// Bot provides a mock function with given fields:
func (_m *LayeredStoreDatabaseLayer) Bot() store.BotStore {
ret := _m.Called()
var r0 store.BotStore
if rf, ok := ret.Get(0).(func() store.BotStore); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.BotStore)
}
}
return r0
}
// Channel provides a mock function with given fields:
func (_m *LayeredStoreDatabaseLayer) Channel() store.ChannelStore {
ret := _m.Called()

View File

@@ -58,6 +58,22 @@ func (_m *SqlStore) Audit() store.AuditStore {
return r0
}
// Bot provides a mock function with given fields:
func (_m *SqlStore) Bot() store.BotStore {
ret := _m.Called()
var r0 store.BotStore
if rf, ok := ret.Get(0).(func() store.BotStore); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.BotStore)
}
}
return r0
}
// Channel provides a mock function with given fields:
func (_m *SqlStore) Channel() store.ChannelStore {
ret := _m.Called()

View File

@@ -28,6 +28,22 @@ func (_m *Store) Audit() store.AuditStore {
return r0
}
// Bot provides a mock function with given fields:
func (_m *Store) Bot() store.BotStore {
ret := _m.Called()
var r0 store.BotStore
if rf, ok := ret.Get(0).(func() store.BotStore); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.BotStore)
}
}
return r0
}
// Channel provides a mock function with given fields:
func (_m *Store) Channel() store.ChannelStore {
ret := _m.Called()

View File

@@ -61,22 +61,6 @@ func (_m *UserStore) AnalyticsGetSystemAdminCount() store.StoreChannel {
return r0
}
// AnalyticsUniqueUserCount provides a mock function with given fields: teamId
func (_m *UserStore) AnalyticsUniqueUserCount(teamId string) store.StoreChannel {
ret := _m.Called(teamId)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok {
r0 = rf(teamId)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// ClearAllCustomRoleAssignments provides a mock function with given fields:
func (_m *UserStore) ClearAllCustomRoleAssignments() store.StoreChannel {
ret := _m.Called()
@@ -98,6 +82,22 @@ func (_m *UserStore) ClearCaches() {
_m.Called()
}
// Count provides a mock function with given fields: options
func (_m *UserStore) Count(options model.UserCountOptions) store.StoreChannel {
ret := _m.Called(options)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(model.UserCountOptions) store.StoreChannel); ok {
r0 = rf(options)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// Get provides a mock function with given fields: id
func (_m *UserStore) Get(id string) store.StoreChannel {
ret := _m.Called(id)
@@ -498,22 +498,6 @@ func (_m *UserStore) GetSystemAdminProfiles() store.StoreChannel {
return r0
}
// GetTotalUsersCount provides a mock function with given fields:
func (_m *UserStore) GetTotalUsersCount() store.StoreChannel {
ret := _m.Called()
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func() store.StoreChannel); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// GetUnreadCount provides a mock function with given fields: userId
func (_m *UserStore) GetUnreadCount(userId string) store.StoreChannel {
ret := _m.Called(userId)

View File

@@ -23,6 +23,7 @@ type Store struct {
ChannelStore mocks.ChannelStore
PostStore mocks.PostStore
UserStore mocks.UserStore
BotStore mocks.BotStore
AuditStore mocks.AuditStore
ClusterDiscoveryStore mocks.ClusterDiscoveryStore
ComplianceStore mocks.ComplianceStore
@@ -55,6 +56,7 @@ func (s *Store) Team() store.TeamStore { return &s.T
func (s *Store) Channel() store.ChannelStore { return &s.ChannelStore }
func (s *Store) Post() store.PostStore { return &s.PostStore }
func (s *Store) User() store.UserStore { return &s.UserStore }
func (s *Store) Bot() store.BotStore { return &s.BotStore }
func (s *Store) Audit() store.AuditStore { return &s.AuditStore }
func (s *Store) ClusterDiscovery() store.ClusterDiscoveryStore { return &s.ClusterDiscoveryStore }
func (s *Store) Compliance() store.ComplianceStore { return &s.ComplianceStore }
@@ -98,6 +100,7 @@ func (s *Store) AssertExpectations(t mock.TestingT) bool {
&s.ChannelStore,
&s.PostStore,
&s.UserStore,
&s.BotStore,
&s.AuditStore,
&s.ClusterDiscoveryStore,
&s.ComplianceStore,

View File

@@ -25,12 +25,14 @@ func TestUserStore(t *testing.T, ss store.Store) {
require.Nil(t, result.Err, "failed cleaning up test user %s", u.Username)
}
t.Run("Count", func(t *testing.T) { testCount(t, ss) })
t.Run("AnalyticsGetInactiveUsersCount", func(t *testing.T) { testUserStoreAnalyticsGetInactiveUsersCount(t, ss) })
t.Run("AnalyticsGetSystemAdminCount", func(t *testing.T) { testUserStoreAnalyticsGetSystemAdminCount(t, ss) })
t.Run("Save", func(t *testing.T) { testUserStoreSave(t, ss) })
t.Run("Update", func(t *testing.T) { testUserStoreUpdate(t, ss) })
t.Run("UpdateUpdateAt", func(t *testing.T) { testUserStoreUpdateUpdateAt(t, ss) })
t.Run("UpdateFailedPasswordAttempts", func(t *testing.T) { testUserStoreUpdateFailedPasswordAttempts(t, ss) })
t.Run("Get", func(t *testing.T) { testUserStoreGet(t, ss) })
t.Run("UserCount", func(t *testing.T) { testUserCount(t, ss) })
t.Run("GetAllUsingAuthService", func(t *testing.T) { testGetAllUsingAuthService(t, ss) })
t.Run("GetAllProfiles", func(t *testing.T) { testUserStoreGetAllProfiles(t, ss) })
t.Run("GetProfiles", func(t *testing.T) { testUserStoreGetProfiles(t, ss) })
@@ -59,8 +61,6 @@ func TestUserStore(t *testing.T, ss store.Store) {
t.Run("SearchInChannel", func(t *testing.T) { testUserStoreSearchInChannel(t, ss) })
t.Run("SearchNotInTeam", func(t *testing.T) { testUserStoreSearchNotInTeam(t, ss) })
t.Run("SearchWithoutTeam", func(t *testing.T) { testUserStoreSearchWithoutTeam(t, ss) })
t.Run("AnalyticsGetInactiveUsersCount", func(t *testing.T) { testUserStoreAnalyticsGetInactiveUsersCount(t, ss) })
t.Run("AnalyticsGetSystemAdminCount", func(t *testing.T) { testUserStoreAnalyticsGetSystemAdminCount(t, ss) })
t.Run("GetProfilesNotInTeam", func(t *testing.T) { testUserStoreGetProfilesNotInTeam(t, ss) })
t.Run("ClearAllCustomRoleAssignments", func(t *testing.T) { testUserStoreClearAllCustomRoleAssignments(t, ss) })
t.Run("GetAllAfter", func(t *testing.T) { testUserStoreGetAllAfter(t, ss) })
@@ -259,6 +259,13 @@ func testUserStoreGet(t *testing.T, ss store.Store) {
Email: MakeEmail(),
Username: model.NewId(),
})).(*model.User)
store.Must(ss.Bot().Save(&model.Bot{
UserId: u2.Id,
Username: u2.Username,
OwnerId: u1.Id,
}))
u2.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u2.Id)) }()
defer func() { store.Must(ss.User().PermanentDelete(u2.Id)) }()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}, -1))
@@ -273,32 +280,19 @@ func testUserStoreGet(t *testing.T, ss store.Store) {
actual := result.Data.(*model.User)
require.Equal(t, u1, actual)
require.False(t, actual.IsBot)
})
t.Run("fetch user 2", func(t *testing.T) {
t.Run("fetch user 2, also a bot", func(t *testing.T) {
result := <-ss.User().Get(u2.Id)
require.Nil(t, result.Err)
actual := result.Data.(*model.User)
require.Equal(t, u2, actual)
require.True(t, actual.IsBot)
})
}
func testUserCount(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
defer func() { store.Must(ss.User().PermanentDelete(u1.Id)) }()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}, -1))
if result := <-ss.User().GetTotalUsersCount(); result.Err != nil {
t.Fatal(result.Err)
} else {
count := result.Data.(int64)
require.False(t, count <= 0, "expected count > 0, got %d", count)
}
}
func testGetAllUsingAuthService(t *testing.T, ss store.Store) {
teamId := model.NewId()
@@ -325,6 +319,13 @@ func testGetAllUsingAuthService(t *testing.T, ss store.Store) {
})).(*model.User)
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}, -1))
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
t.Run("get by unknown auth service", func(t *testing.T) {
@@ -372,6 +373,13 @@ func testUserStoreGetAllProfiles(t *testing.T, ss store.Store) {
Email: MakeEmail(),
Username: "u3" + model.NewId(),
})).(*model.User)
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
u4 := store.Must(ss.User().Save(&model.User{
@@ -529,6 +537,13 @@ func testUserStoreGetProfiles(t *testing.T, ss store.Store) {
Email: MakeEmail(),
Username: "u3" + model.NewId(),
})).(*model.User)
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}, -1))
@@ -660,6 +675,13 @@ func testUserStoreGetProfilesInChannel(t *testing.T, ss store.Store) {
})).(*model.User)
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}, -1))
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
c1 := store.Must(ss.Channel().Save(&model.Channel{
TeamId: teamId,
@@ -741,6 +763,13 @@ func testUserStoreGetProfilesInChannelByStatus(t *testing.T, ss store.Store) {
})).(*model.User)
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}, -1))
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
c1 := store.Must(ss.Channel().Save(&model.Channel{
TeamId: teamId,
@@ -827,6 +856,13 @@ func testUserStoreGetProfilesWithoutTeam(t *testing.T, ss store.Store) {
Username: "u3" + model.NewId(),
})).(*model.User)
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
t.Run("get, offset 0, limit 100", func(t *testing.T) {
result := <-ss.User().GetProfilesWithoutTeam(0, 100)
@@ -870,6 +906,13 @@ func testUserStoreGetAllProfilesInChannel(t *testing.T, ss store.Store) {
})).(*model.User)
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}, -1))
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
c1 := store.Must(ss.Channel().Save(&model.Channel{
TeamId: teamId,
@@ -970,6 +1013,13 @@ func testUserStoreGetProfilesNotInChannel(t *testing.T, ss store.Store) {
})).(*model.User)
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}, -1))
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
c1 := store.Must(ss.Channel().Save(&model.Channel{
TeamId: teamId,
@@ -1068,6 +1118,13 @@ func testUserStoreGetProfilesByIds(t *testing.T, ss store.Store) {
})).(*model.User)
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}, -1))
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
t.Run("get u1 by id, no caching", func(t *testing.T) {
result := <-ss.User().GetProfileByIds([]string{u1.Id}, false)
@@ -1124,6 +1181,13 @@ func testUserStoreGetProfilesByUsernames(t *testing.T, ss store.Store) {
})).(*model.User)
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: team2Id, UserId: u3.Id}, -1))
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
t.Run("get by u1 and u2 usernames, team id 1", func(t *testing.T) {
result := <-ss.User().GetProfilesByUsernames([]string{u1.Username, u2.Username}, teamId)
@@ -1181,6 +1245,13 @@ func testUserStoreGetSystemAdminProfiles(t *testing.T, ss store.Store) {
})).(*model.User)
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}, -1))
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
t.Run("all system admin profiles", func(t *testing.T) {
result := <-ss.User().GetSystemAdminProfiles()
@@ -1215,6 +1286,13 @@ func testUserStoreGetByEmail(t *testing.T, ss store.Store) {
})).(*model.User)
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}, -1))
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
t.Run("get u1 by email", func(t *testing.T) {
result := <-ss.User().GetByEmail(u1.Email)
@@ -1276,6 +1354,13 @@ func testUserStoreGetByAuthData(t *testing.T, ss store.Store) {
})).(*model.User)
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}, -1))
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
t.Run("get by u1 auth", func(t *testing.T) {
result := <-ss.User().GetByAuth(u1.AuthData, u1.AuthService)
@@ -1333,6 +1418,13 @@ func testUserStoreGetByUsername(t *testing.T, ss store.Store) {
})).(*model.User)
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}, -1))
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
t.Run("get u1 by username", func(t *testing.T) {
result := <-ss.User().GetByUsername(u1.Username)
@@ -1397,6 +1489,13 @@ func testUserStoreGetForLogin(t *testing.T, ss store.Store) {
})).(*model.User)
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}, -1))
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
t.Run("get u1 by username, allow both", func(t *testing.T) {
result := <-ss.User().GetForLogin(u1.Username, true, true)
@@ -1666,6 +1765,13 @@ func testUserStoreGetRecentlyActiveUsersForTeam(t *testing.T, ss store.Store) {
})).(*model.User)
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}, -1))
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
millis := model.GetMillis()
u3.LastActivityAt = millis
@@ -1727,6 +1833,13 @@ func testUserStoreGetNewUsersForTeam(t *testing.T, ss store.Store) {
})).(*model.User)
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}, -1))
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
u4 := store.Must(ss.User().Save(&model.User{
Email: MakeEmail(),
@@ -1830,6 +1943,13 @@ func testUserStoreSearch(t *testing.T, ss store.Store) {
}
store.Must(ss.User().Save(u3))
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
u5 := &model.User{
Username: "yu" + model.NewId(),
@@ -2160,6 +2280,13 @@ func testUserStoreSearchNotInChannel(t *testing.T, ss store.Store) {
}
store.Must(ss.User().Save(u3))
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
tid := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u1.Id}, -1))
@@ -2367,6 +2494,13 @@ func testUserStoreSearchInChannel(t *testing.T, ss store.Store) {
}
store.Must(ss.User().Save(u3))
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
tid := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u1.Id}, -1))
@@ -2513,6 +2647,13 @@ func testUserStoreSearchNotInTeam(t *testing.T, ss store.Store) {
}
store.Must(ss.User().Save(u3))
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
u4 := &model.User{
Username: "simon" + model.NewId(),
@@ -2685,6 +2826,13 @@ func testUserStoreSearchWithoutTeam(t *testing.T, ss store.Store) {
}
store.Must(ss.User().Save(u3))
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
tid := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u3.Id}, -1))
@@ -2753,6 +2901,94 @@ func testUserStoreSearchWithoutTeam(t *testing.T, ss store.Store) {
}
}
func testCount(t *testing.T, ss store.Store) {
// Regular
teamId := model.NewId()
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
defer func() { store.Must(ss.User().PermanentDelete(u1.Id)) }()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, -1))
// Deleted
u2 := &model.User{}
u2.Email = MakeEmail()
u2.DeleteAt = model.GetMillis()
store.Must(ss.User().Save(u2))
defer func() { store.Must(ss.User().PermanentDelete(u2.Id)) }()
// Bot
u3 := store.Must(ss.User().Save(&model.User{
Email: MakeEmail(),
})).(*model.User)
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
result := <-ss.User().Count(model.UserCountOptions{
IncludeBotAccounts: false,
IncludeDeleted: false,
TeamId: "",
})
require.Nil(t, result.Err)
require.Equal(t, int64(1), result.Data.(int64))
result = <-ss.User().Count(model.UserCountOptions{
IncludeBotAccounts: true,
IncludeDeleted: false,
TeamId: "",
})
require.Nil(t, result.Err)
require.Equal(t, int64(2), result.Data.(int64))
result = <-ss.User().Count(model.UserCountOptions{
IncludeBotAccounts: false,
IncludeDeleted: true,
TeamId: "",
})
require.Nil(t, result.Err)
require.Equal(t, int64(2), result.Data.(int64))
result = <-ss.User().Count(model.UserCountOptions{
IncludeBotAccounts: true,
IncludeDeleted: true,
TeamId: "",
})
require.Nil(t, result.Err)
require.Equal(t, int64(3), result.Data.(int64))
result = <-ss.User().Count(model.UserCountOptions{
IncludeBotAccounts: true,
IncludeDeleted: true,
ExcludeRegularUsers: true,
TeamId: "",
})
require.Nil(t, result.Err)
require.Equal(t, int64(1), result.Data.(int64))
result = <-ss.User().Count(model.UserCountOptions{
IncludeBotAccounts: true,
IncludeDeleted: true,
TeamId: teamId,
})
require.Nil(t, result.Err)
require.Equal(t, int64(1), result.Data.(int64))
result = <-ss.User().Count(model.UserCountOptions{
IncludeBotAccounts: true,
IncludeDeleted: true,
TeamId: model.NewId(),
})
require.Nil(t, result.Err)
require.Equal(t, int64(0), result.Data.(int64))
}
func testUserStoreAnalyticsGetInactiveUsersCount(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
@@ -2849,6 +3085,13 @@ func testUserStoreGetProfilesNotInTeam(t *testing.T, ss store.Store) {
Username: "u3" + model.NewId(),
})).(*model.User)
defer func() { store.Must(ss.User().PermanentDelete(u3.Id)) }()
store.Must(ss.Bot().Save(&model.Bot{
UserId: u3.Id,
Username: u3.Username,
OwnerId: u1.Id,
}))
u3.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u3.Id)) }()
var etag1, etag2, etag3 string
@@ -3030,6 +3273,13 @@ func testUserStoreGetAllAfter(t *testing.T, ss store.Store) {
Username: "u2" + model.NewId(),
})).(*model.User)
defer func() { store.Must(ss.User().PermanentDelete(u2.Id)) }()
store.Must(ss.Bot().Save(&model.Bot{
UserId: u2.Id,
Username: u2.Username,
OwnerId: u1.Id,
}))
u2.IsBot = true
defer func() { store.Must(ss.Bot().PermanentDelete(u2.Id)) }()
expected := []*model.User{u1, u2}
if strings.Compare(u2.Id, u1.Id) < 0 {

View File

@@ -257,17 +257,15 @@ func columnToFieldIndex(m *DbMap, t reflect.Type, name string, cols []string) ([
cArguments := strings.Split(field.Tag.Get("db"), ",")
fieldName = cArguments[0]
if fieldName == "-" {
return false
} else if fieldName == "" {
fieldName = field.Name
}
if tableMapped {
colMap := colMapOrNil(table, fieldName)
if colMap != nil {
fieldName = colMap.ColumnName
}
}
if fieldName == "" || fieldName == "-" {
fieldName = field.Name
}
return colName == strings.ToLower(fieldName)
})
if found {

View File

@@ -182,7 +182,7 @@ func NewInvalidUrlParamError(parameter string) *model.AppError {
}
func (c *Context) SetPermissionError(permission *model.Permission) {
c.Err = model.NewAppError("Permissions", "api.context.permissions.app_error", nil, "userId="+c.App.Session.UserId+", "+"permission="+permission.Id, http.StatusForbidden)
c.Err = c.App.MakePermissionError(permission)
}
func (c *Context) SetSiteURLHeader(url string) {
@@ -563,3 +563,14 @@ func (c *Context) RequireSyncableType() *Context {
}
return c
}
func (c *Context) RequireBotUserId() *Context {
if c.Err != nil {
return c
}
if len(c.Params.BotUserId) != 26 {
c.SetInvalidUrlParam("bot_user_id")
}
return c
}

View File

@@ -58,6 +58,7 @@ type Params struct {
RemoteId string
SyncableId string
SyncableType model.GroupSyncableType
BotUserId string
}
func ParamsFromRequest(r *http.Request) *Params {
@@ -226,5 +227,10 @@ func ParamsFromRequest(r *http.Request) *Params {
params.SyncableType = model.GroupSyncableTypeChannel
}
}
if val, ok := props["bot_user_id"]; ok {
params.BotUserId = val
}
return params
}