mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Mm 23710 mmctl local mode (#14561)
* [MM-24146] Add unix socket listener for mmctl local mode (#14296) * add unix socket listener for mmctl local mode * add a constant for local-mode socket path * reflect review comments * [MM-24401] Base approach for Local Mode (#14333) * add unix socket listener for mmctl local mode * First working PoC * Adds the channel list endpoint * Add team list endpoint * Add a LocalClient to the api test helper and start local mode * Add helper to test with both SystemAdmin and Local clients * Add some docs * Adds TestForAllClients test helper * Incorporating @ashishbhate's proposal for adding test names to the helpers * Fix init errors after merge * Adds create channel tests * Always init local mode to allow for enabling-disabling it via config * Check the RemoteAddr of the request before marking session as local * Mark the request as errored if it's local and the origin is remote * Set the socket permissions to read/write when initialising * Fix linter * Replace RemoteAddr check to ditch connections with the IP:PORT shape Co-authored-by: Ibrahim Serdar Acikgoz <serdaracikgoz86@gmail.com> * Fix translations order * [MM-24832] Migrate plugin endpoints to local mode (#14543) * [MM-24832] Migrate plugin endpoints to local mode * Fix client reference in helper * [MM-24776] Migrate config endpoints to local mode (#14544) * [MM-24776] Migrate get config endpoint to local mode * [MM-24777] Migrate update config endpoint to local mode * Fix update config to bypass RestrictSystemAdmin flag * Add patchConfig endpoint * MM-24774/MM-24755: local mode for addLicense and removeLicense (#14491) Automatic Merge Co-authored-by: Ibrahim Serdar Acikgoz <serdaracikgoz86@gmail.com> Co-authored-by: Ashish Bhate <bhate.ashish@gmail.com>
This commit is contained in:
committed by
GitHub
parent
e8081b7a0f
commit
0d89ff5d0e
30
api4/api.go
30
api4/api.go
@@ -255,6 +255,36 @@ func Init(configservice configservice.ConfigService, globalOptionsFunc app.AppOp
|
||||
return api
|
||||
}
|
||||
|
||||
func InitLocal(configservice configservice.ConfigService, globalOptionsFunc app.AppOptionCreator, root *mux.Router) *API {
|
||||
api := &API{
|
||||
ConfigService: configservice,
|
||||
GetGlobalAppOptions: globalOptionsFunc,
|
||||
BaseRoutes: &Routes{},
|
||||
}
|
||||
|
||||
api.BaseRoutes.Root = root
|
||||
api.BaseRoutes.ApiRoot = root.PathPrefix(model.API_URL_SUFFIX).Subrouter()
|
||||
|
||||
api.BaseRoutes.Teams = api.BaseRoutes.ApiRoot.PathPrefix("/teams").Subrouter()
|
||||
|
||||
api.BaseRoutes.Channels = api.BaseRoutes.ApiRoot.PathPrefix("/channels").Subrouter()
|
||||
|
||||
api.BaseRoutes.License = api.BaseRoutes.ApiRoot.PathPrefix("/license").Subrouter()
|
||||
|
||||
api.BaseRoutes.Plugins = api.BaseRoutes.ApiRoot.PathPrefix("/plugins").Subrouter()
|
||||
api.BaseRoutes.Plugin = api.BaseRoutes.Plugins.PathPrefix("/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}").Subrouter()
|
||||
|
||||
api.InitTeamLocal()
|
||||
api.InitChannelLocal()
|
||||
api.InitLicenseLocal()
|
||||
api.InitConfigLocal()
|
||||
api.InitPluginLocal()
|
||||
|
||||
root.Handle("/api/v4/{anything:.*}", http.HandlerFunc(api.Handle404))
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
func (api *API) Handle404(w http.ResponseWriter, r *http.Request) {
|
||||
web.Handle404(api.ConfigService, w, r)
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ type TestHelper struct {
|
||||
SystemAdminUser *model.User
|
||||
tempWorkspace string
|
||||
|
||||
LocalClient *model.Client4
|
||||
|
||||
IncludeCacheLayer bool
|
||||
}
|
||||
|
||||
@@ -78,6 +80,8 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent
|
||||
config := memoryStore.Get()
|
||||
*config.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins")
|
||||
*config.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp")
|
||||
config.ServiceSettings.EnableLocalMode = model.NewBool(true)
|
||||
*config.ServiceSettings.LocalModeSocketLocation = filepath.Join(tempWorkspace, "mattermost_local.sock")
|
||||
if updateConfig != nil {
|
||||
updateConfig(config)
|
||||
}
|
||||
@@ -118,13 +122,13 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent
|
||||
})
|
||||
prevListenAddress := *th.App.Config().ServiceSettings.ListenAddress
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" })
|
||||
serverErr := th.Server.Start()
|
||||
if serverErr != nil {
|
||||
panic(serverErr)
|
||||
if err := th.Server.Start(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress })
|
||||
Init(th.Server, th.Server.AppOptions, th.App.Srv().Router)
|
||||
InitLocal(th.Server, th.Server.AppOptions, th.App.Srv().LocalRouter)
|
||||
web.New(th.Server, th.Server.AppOptions, th.App.Srv().Router)
|
||||
wsapi.Init(th.App, th.App.Srv().WebSocketRouter)
|
||||
th.App.DoAppMigrations()
|
||||
@@ -149,6 +153,8 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent
|
||||
th.Client = th.CreateClient()
|
||||
th.SystemAdminClient = th.CreateClient()
|
||||
|
||||
th.LocalClient = th.CreateLocalClient(*config.ServiceSettings.LocalModeSocketLocation)
|
||||
|
||||
if th.tempWorkspace == "" {
|
||||
th.tempWorkspace = tempWorkspace
|
||||
}
|
||||
@@ -343,6 +349,22 @@ func (me *TestHelper) CreateClient() *model.Client4 {
|
||||
return model.NewAPIv4Client(fmt.Sprintf("http://localhost:%v", me.App.Srv().ListenAddr.Port))
|
||||
}
|
||||
|
||||
// ToDo: maybe move this to NewAPIv4SocketClient and reuse it in mmctl
|
||||
func (me *TestHelper) CreateLocalClient(socketPath string) *model.Client4 {
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: func(network, addr string) (net.Conn, error) {
|
||||
return net.Dial("unix", socketPath)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return &model.Client4{
|
||||
ApiUrl: "http://_" + model.API_URL_SUFFIX,
|
||||
HttpClient: httpClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (me *TestHelper) CreateWebSocketClient() (*model.WebSocketClient, *model.AppError) {
|
||||
return model.NewWebSocketClient4(fmt.Sprintf("ws://localhost:%v", me.App.Srv().ListenAddr.Port), me.Client.AuthToken)
|
||||
}
|
||||
@@ -671,6 +693,46 @@ func (me *TestHelper) CreateGroup() *model.Group {
|
||||
return group
|
||||
}
|
||||
|
||||
// TestForSystemAdminAndLocal runs a test function for both
|
||||
// SystemAdmin and Local clients. Several endpoints work in the same
|
||||
// way when used by a fully privileged user and through the local
|
||||
// mode, so this helper facilitates checking both
|
||||
func (me *TestHelper) TestForSystemAdminAndLocal(t *testing.T, f func(*testing.T, *model.Client4), name ...string) {
|
||||
var testName string
|
||||
if len(name) > 0 {
|
||||
testName = name[0] + "/"
|
||||
}
|
||||
|
||||
t.Run(testName+"SystemAdminClient", func(t *testing.T) {
|
||||
f(t, me.SystemAdminClient)
|
||||
})
|
||||
|
||||
t.Run(testName+"LocalClient", func(t *testing.T) {
|
||||
f(t, me.LocalClient)
|
||||
})
|
||||
}
|
||||
|
||||
// TestForAllClients runs a test function for all the clients
|
||||
// registered in the TestHelper
|
||||
func (me *TestHelper) TestForAllClients(t *testing.T, f func(*testing.T, *model.Client4), name ...string) {
|
||||
var testName string
|
||||
if len(name) > 0 {
|
||||
testName = name[0] + "/"
|
||||
}
|
||||
|
||||
t.Run(testName+"Client", func(t *testing.T) {
|
||||
f(t, me.Client)
|
||||
})
|
||||
|
||||
t.Run(testName+"SystemAdminClient", func(t *testing.T) {
|
||||
f(t, me.SystemAdminClient)
|
||||
})
|
||||
|
||||
t.Run(testName+"LocalClient", func(t *testing.T) {
|
||||
f(t, me.LocalClient)
|
||||
})
|
||||
}
|
||||
|
||||
func GenerateTestUsername() string {
|
||||
return "fakeuser" + model.NewRandomString(10)
|
||||
}
|
||||
|
||||
41
api4/channel_local.go
Normal file
41
api4/channel_local.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/audit"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
)
|
||||
|
||||
func (api *API) InitChannelLocal() {
|
||||
api.BaseRoutes.Channels.Handle("", api.ApiLocal(getAllChannels)).Methods("GET")
|
||||
api.BaseRoutes.Channels.Handle("", api.ApiLocal(localCreateChannel)).Methods("POST")
|
||||
}
|
||||
|
||||
func localCreateChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
channel := model.ChannelFromJson(r.Body)
|
||||
if channel == nil {
|
||||
c.SetInvalidParam("channel")
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("localCreateChannel", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
auditRec.AddMeta("channel", channel)
|
||||
|
||||
sc, err := c.App.CreateChannel(channel, false)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
auditRec.AddMeta("channel", sc) // overwrite meta
|
||||
c.LogAudit("name=" + channel.Name)
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write([]byte(sc.ToJson()))
|
||||
}
|
||||
@@ -106,13 +106,15 @@ func TestCreateChannel(t *testing.T) {
|
||||
_, resp = Client.CreateChannel(private)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
channel.Name = GenerateTestChannelName()
|
||||
_, resp = th.SystemAdminClient.CreateChannel(channel)
|
||||
CheckNoError(t, resp)
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
channel.Name = GenerateTestChannelName()
|
||||
_, resp = client.CreateChannel(channel)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
private.Name = GenerateTestChannelName()
|
||||
_, resp = th.SystemAdminClient.CreateChannel(private)
|
||||
CheckNoError(t, resp)
|
||||
private.Name = GenerateTestChannelName()
|
||||
_, resp = client.CreateChannel(private)
|
||||
CheckNoError(t, resp)
|
||||
})
|
||||
|
||||
// Test posting Garbage
|
||||
r, err := Client.DoApiPost("/channels", "garbage")
|
||||
@@ -901,28 +903,30 @@ func TestGetAllChannels(t *testing.T) {
|
||||
defer th.TearDown()
|
||||
Client := th.Client
|
||||
|
||||
channels, resp := th.SystemAdminClient.GetAllChannels(0, 20, "")
|
||||
CheckNoError(t, resp)
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
channels, resp := client.GetAllChannels(0, 20, "")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
// At least, all the not-deleted channels created during the InitBasic
|
||||
require.True(t, len(*channels) >= 3)
|
||||
for _, c := range *channels {
|
||||
require.NotEqual(t, c.TeamId, "")
|
||||
}
|
||||
// At least, all the not-deleted channels created during the InitBasic
|
||||
require.True(t, len(*channels) >= 3)
|
||||
for _, c := range *channels {
|
||||
require.NotEqual(t, c.TeamId, "")
|
||||
}
|
||||
|
||||
channels, resp = th.SystemAdminClient.GetAllChannels(0, 10, "")
|
||||
CheckNoError(t, resp)
|
||||
require.True(t, len(*channels) >= 3)
|
||||
channels, resp = client.GetAllChannels(0, 10, "")
|
||||
CheckNoError(t, resp)
|
||||
require.True(t, len(*channels) >= 3)
|
||||
|
||||
channels, resp = th.SystemAdminClient.GetAllChannels(1, 1, "")
|
||||
CheckNoError(t, resp)
|
||||
require.Len(t, *channels, 1)
|
||||
channels, resp = client.GetAllChannels(1, 1, "")
|
||||
CheckNoError(t, resp)
|
||||
require.Len(t, *channels, 1)
|
||||
|
||||
channels, resp = th.SystemAdminClient.GetAllChannels(10000, 10000, "")
|
||||
CheckNoError(t, resp)
|
||||
require.Empty(t, *channels)
|
||||
channels, resp = client.GetAllChannels(10000, 10000, "")
|
||||
CheckNoError(t, resp)
|
||||
require.Empty(t, *channels)
|
||||
})
|
||||
|
||||
_, resp = Client.GetAllChannels(0, 20, "")
|
||||
_, resp := Client.GetAllChannels(0, 20, "")
|
||||
CheckForbiddenStatus(t, resp)
|
||||
}
|
||||
|
||||
|
||||
112
api4/config_local.go
Normal file
112
api4/config_local.go
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/audit"
|
||||
"github.com/mattermost/mattermost-server/v5/config"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/utils"
|
||||
)
|
||||
|
||||
func (api *API) InitConfigLocal() {
|
||||
api.BaseRoutes.ApiRoot.Handle("/config", api.ApiLocal(getConfig)).Methods("GET")
|
||||
api.BaseRoutes.ApiRoot.Handle("/config", api.ApiLocal(localUpdateConfig)).Methods("PUT")
|
||||
api.BaseRoutes.ApiRoot.Handle("/config/patch", api.ApiLocal(localPatchConfig)).Methods("PUT")
|
||||
}
|
||||
|
||||
func localUpdateConfig(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
cfg := model.ConfigFromJson(r.Body)
|
||||
if cfg == nil {
|
||||
c.SetInvalidParam("config")
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("localUpdateConfig", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
|
||||
cfg.SetDefaults()
|
||||
|
||||
appCfg := c.App.Config()
|
||||
|
||||
// Do not allow plugin uploads to be toggled through the API
|
||||
cfg.PluginSettings.EnableUploads = appCfg.PluginSettings.EnableUploads
|
||||
|
||||
// Do not allow certificates to be changed through the API
|
||||
cfg.PluginSettings.SignaturePublicKeyFiles = appCfg.PluginSettings.SignaturePublicKeyFiles
|
||||
|
||||
c.App.HandleMessageExportConfig(cfg, appCfg)
|
||||
|
||||
err := cfg.IsValid()
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
err = c.App.SaveConfig(cfg, true)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
cfg = c.App.GetSanitizedConfig()
|
||||
|
||||
auditRec.Success()
|
||||
c.LogAudit("updateConfig")
|
||||
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
w.Write([]byte(cfg.ToJson()))
|
||||
}
|
||||
|
||||
func localPatchConfig(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
cfg := model.ConfigFromJson(r.Body)
|
||||
if cfg == nil {
|
||||
c.SetInvalidParam("config")
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("localPatchConfig", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
|
||||
appCfg := c.App.Config()
|
||||
filterFn := func(structField reflect.StructField, base, patch reflect.Value) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Do not allow plugin uploads to be toggled through the API
|
||||
cfg.PluginSettings.EnableUploads = appCfg.PluginSettings.EnableUploads
|
||||
|
||||
if cfg.MessageExportSettings.EnableExport != nil {
|
||||
c.App.HandleMessageExportConfig(cfg, appCfg)
|
||||
}
|
||||
|
||||
updatedCfg, mergeErr := config.Merge(appCfg, cfg, &utils.MergeConfig{
|
||||
StructFieldFilter: filterFn,
|
||||
})
|
||||
|
||||
if mergeErr != nil {
|
||||
c.Err = model.NewAppError("patchConfig", "api.config.update_config.restricted_merge.app_error", nil, mergeErr.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err := updatedCfg.IsValid()
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
err = c.App.SaveConfig(updatedCfg, true)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
w.Write([]byte(c.App.GetSanitizedConfig().ToJson()))
|
||||
}
|
||||
@@ -22,33 +22,35 @@ func TestGetConfig(t *testing.T) {
|
||||
_, resp := Client.GetConfig()
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
cfg, resp := th.SystemAdminClient.GetConfig()
|
||||
CheckNoError(t, resp)
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
cfg, resp := client.GetConfig()
|
||||
CheckNoError(t, resp)
|
||||
|
||||
require.NotEqual(t, "", cfg.TeamSettings.SiteName)
|
||||
require.NotEqual(t, "", cfg.TeamSettings.SiteName)
|
||||
|
||||
if *cfg.LdapSettings.BindPassword != model.FAKE_SETTING && len(*cfg.LdapSettings.BindPassword) != 0 {
|
||||
require.FailNow(t, "did not sanitize properly")
|
||||
}
|
||||
require.Equal(t, model.FAKE_SETTING, *cfg.FileSettings.PublicLinkSalt, "did not sanitize properly")
|
||||
if *cfg.LdapSettings.BindPassword != model.FAKE_SETTING && len(*cfg.LdapSettings.BindPassword) != 0 {
|
||||
require.FailNow(t, "did not sanitize properly")
|
||||
}
|
||||
require.Equal(t, model.FAKE_SETTING, *cfg.FileSettings.PublicLinkSalt, "did not sanitize properly")
|
||||
|
||||
if *cfg.FileSettings.AmazonS3SecretAccessKey != model.FAKE_SETTING && len(*cfg.FileSettings.AmazonS3SecretAccessKey) != 0 {
|
||||
require.FailNow(t, "did not sanitize properly")
|
||||
}
|
||||
if *cfg.EmailSettings.SMTPPassword != model.FAKE_SETTING && len(*cfg.EmailSettings.SMTPPassword) != 0 {
|
||||
require.FailNow(t, "did not sanitize properly")
|
||||
}
|
||||
if *cfg.GitLabSettings.Secret != model.FAKE_SETTING && len(*cfg.GitLabSettings.Secret) != 0 {
|
||||
require.FailNow(t, "did not sanitize properly")
|
||||
}
|
||||
require.Equal(t, model.FAKE_SETTING, *cfg.SqlSettings.DataSource, "did not sanitize properly")
|
||||
require.Equal(t, model.FAKE_SETTING, *cfg.SqlSettings.AtRestEncryptKey, "did not sanitize properly")
|
||||
if !strings.Contains(strings.Join(cfg.SqlSettings.DataSourceReplicas, " "), model.FAKE_SETTING) && len(cfg.SqlSettings.DataSourceReplicas) != 0 {
|
||||
require.FailNow(t, "did not sanitize properly")
|
||||
}
|
||||
if !strings.Contains(strings.Join(cfg.SqlSettings.DataSourceSearchReplicas, " "), model.FAKE_SETTING) && len(cfg.SqlSettings.DataSourceSearchReplicas) != 0 {
|
||||
require.FailNow(t, "did not sanitize properly")
|
||||
}
|
||||
if *cfg.FileSettings.AmazonS3SecretAccessKey != model.FAKE_SETTING && len(*cfg.FileSettings.AmazonS3SecretAccessKey) != 0 {
|
||||
require.FailNow(t, "did not sanitize properly")
|
||||
}
|
||||
if *cfg.EmailSettings.SMTPPassword != model.FAKE_SETTING && len(*cfg.EmailSettings.SMTPPassword) != 0 {
|
||||
require.FailNow(t, "did not sanitize properly")
|
||||
}
|
||||
if *cfg.GitLabSettings.Secret != model.FAKE_SETTING && len(*cfg.GitLabSettings.Secret) != 0 {
|
||||
require.FailNow(t, "did not sanitize properly")
|
||||
}
|
||||
require.Equal(t, model.FAKE_SETTING, *cfg.SqlSettings.DataSource, "did not sanitize properly")
|
||||
require.Equal(t, model.FAKE_SETTING, *cfg.SqlSettings.AtRestEncryptKey, "did not sanitize properly")
|
||||
if !strings.Contains(strings.Join(cfg.SqlSettings.DataSourceReplicas, " "), model.FAKE_SETTING) && len(cfg.SqlSettings.DataSourceReplicas) != 0 {
|
||||
require.FailNow(t, "did not sanitize properly")
|
||||
}
|
||||
if !strings.Contains(strings.Join(cfg.SqlSettings.DataSourceSearchReplicas, " "), model.FAKE_SETTING) && len(cfg.SqlSettings.DataSourceSearchReplicas) != 0 {
|
||||
require.FailNow(t, "did not sanitize properly")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestReloadConfig(t *testing.T) {
|
||||
@@ -88,66 +90,68 @@ func TestUpdateConfig(t *testing.T) {
|
||||
_, resp = Client.UpdateConfig(cfg)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
SiteName := th.App.Config().TeamSettings.SiteName
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
SiteName := th.App.Config().TeamSettings.SiteName
|
||||
|
||||
*cfg.TeamSettings.SiteName = "MyFancyName"
|
||||
cfg, resp = th.SystemAdminClient.UpdateConfig(cfg)
|
||||
CheckNoError(t, resp)
|
||||
*cfg.TeamSettings.SiteName = "MyFancyName"
|
||||
cfg, resp = client.UpdateConfig(cfg)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
require.Equal(t, "MyFancyName", *cfg.TeamSettings.SiteName, "It should update the SiteName")
|
||||
require.Equal(t, "MyFancyName", *cfg.TeamSettings.SiteName, "It should update the SiteName")
|
||||
|
||||
//Revert the change
|
||||
cfg.TeamSettings.SiteName = SiteName
|
||||
cfg, resp = th.SystemAdminClient.UpdateConfig(cfg)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
require.Equal(t, SiteName, cfg.TeamSettings.SiteName, "It should update the SiteName")
|
||||
|
||||
t.Run("Should set defaults for missing fields", func(t *testing.T) {
|
||||
_, appErr := th.SystemAdminClient.DoApiPut(th.SystemAdminClient.GetConfigRoute(), `{"ServiceSettings":{}}`)
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
|
||||
t.Run("Should fail with validation error if invalid config setting is passed", func(t *testing.T) {
|
||||
//Revert the change
|
||||
badcfg := cfg.Clone()
|
||||
badcfg.PasswordSettings.MinimumLength = model.NewInt(4)
|
||||
badcfg.PasswordSettings.MinimumLength = model.NewInt(4)
|
||||
_, resp = th.SystemAdminClient.UpdateConfig(badcfg)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
CheckErrorMessage(t, resp, "model.config.is_valid.password_length.app_error")
|
||||
})
|
||||
|
||||
t.Run("Should not be able to modify PluginSettings.EnableUploads", func(t *testing.T) {
|
||||
oldEnableUploads := *th.App.Config().PluginSettings.EnableUploads
|
||||
*cfg.PluginSettings.EnableUploads = !oldEnableUploads
|
||||
|
||||
cfg, resp = th.SystemAdminClient.UpdateConfig(cfg)
|
||||
cfg.TeamSettings.SiteName = SiteName
|
||||
cfg, resp = client.UpdateConfig(cfg)
|
||||
CheckNoError(t, resp)
|
||||
assert.Equal(t, oldEnableUploads, *cfg.PluginSettings.EnableUploads)
|
||||
assert.Equal(t, oldEnableUploads, *th.App.Config().PluginSettings.EnableUploads)
|
||||
|
||||
cfg.PluginSettings.EnableUploads = nil
|
||||
cfg, resp = th.SystemAdminClient.UpdateConfig(cfg)
|
||||
CheckNoError(t, resp)
|
||||
assert.Equal(t, oldEnableUploads, *cfg.PluginSettings.EnableUploads)
|
||||
assert.Equal(t, oldEnableUploads, *th.App.Config().PluginSettings.EnableUploads)
|
||||
})
|
||||
require.Equal(t, SiteName, cfg.TeamSettings.SiteName, "It should update the SiteName")
|
||||
|
||||
t.Run("Should not be able to modify PluginSettings.SignaturePublicKeyFiles", func(t *testing.T) {
|
||||
oldPublicKeys := th.App.Config().PluginSettings.SignaturePublicKeyFiles
|
||||
cfg.PluginSettings.SignaturePublicKeyFiles = append(cfg.PluginSettings.SignaturePublicKeyFiles, "new_signature")
|
||||
t.Run("Should set defaults for missing fields", func(t *testing.T) {
|
||||
_, appErr := th.SystemAdminClient.DoApiPut(th.SystemAdminClient.GetConfigRoute(), `{"ServiceSettings":{}}`)
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
|
||||
cfg, resp = th.SystemAdminClient.UpdateConfig(cfg)
|
||||
CheckNoError(t, resp)
|
||||
assert.Equal(t, oldPublicKeys, cfg.PluginSettings.SignaturePublicKeyFiles)
|
||||
assert.Equal(t, oldPublicKeys, th.App.Config().PluginSettings.SignaturePublicKeyFiles)
|
||||
t.Run("Should fail with validation error if invalid config setting is passed", func(t *testing.T) {
|
||||
//Revert the change
|
||||
badcfg := cfg.Clone()
|
||||
badcfg.PasswordSettings.MinimumLength = model.NewInt(4)
|
||||
badcfg.PasswordSettings.MinimumLength = model.NewInt(4)
|
||||
_, resp = client.UpdateConfig(badcfg)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
CheckErrorMessage(t, resp, "model.config.is_valid.password_length.app_error")
|
||||
})
|
||||
|
||||
cfg.PluginSettings.SignaturePublicKeyFiles = nil
|
||||
cfg, resp = th.SystemAdminClient.UpdateConfig(cfg)
|
||||
CheckNoError(t, resp)
|
||||
assert.Equal(t, oldPublicKeys, cfg.PluginSettings.SignaturePublicKeyFiles)
|
||||
assert.Equal(t, oldPublicKeys, th.App.Config().PluginSettings.SignaturePublicKeyFiles)
|
||||
t.Run("Should not be able to modify PluginSettings.EnableUploads", func(t *testing.T) {
|
||||
oldEnableUploads := *th.App.Config().PluginSettings.EnableUploads
|
||||
*cfg.PluginSettings.EnableUploads = !oldEnableUploads
|
||||
|
||||
cfg, resp = client.UpdateConfig(cfg)
|
||||
CheckNoError(t, resp)
|
||||
assert.Equal(t, oldEnableUploads, *cfg.PluginSettings.EnableUploads)
|
||||
assert.Equal(t, oldEnableUploads, *th.App.Config().PluginSettings.EnableUploads)
|
||||
|
||||
cfg.PluginSettings.EnableUploads = nil
|
||||
cfg, resp = client.UpdateConfig(cfg)
|
||||
CheckNoError(t, resp)
|
||||
assert.Equal(t, oldEnableUploads, *cfg.PluginSettings.EnableUploads)
|
||||
assert.Equal(t, oldEnableUploads, *th.App.Config().PluginSettings.EnableUploads)
|
||||
})
|
||||
|
||||
t.Run("Should not be able to modify PluginSettings.SignaturePublicKeyFiles", func(t *testing.T) {
|
||||
oldPublicKeys := th.App.Config().PluginSettings.SignaturePublicKeyFiles
|
||||
cfg.PluginSettings.SignaturePublicKeyFiles = append(cfg.PluginSettings.SignaturePublicKeyFiles, "new_signature")
|
||||
|
||||
cfg, resp = client.UpdateConfig(cfg)
|
||||
CheckNoError(t, resp)
|
||||
assert.Equal(t, oldPublicKeys, cfg.PluginSettings.SignaturePublicKeyFiles)
|
||||
assert.Equal(t, oldPublicKeys, th.App.Config().PluginSettings.SignaturePublicKeyFiles)
|
||||
|
||||
cfg.PluginSettings.SignaturePublicKeyFiles = nil
|
||||
cfg, resp = client.UpdateConfig(cfg)
|
||||
CheckNoError(t, resp)
|
||||
assert.Equal(t, oldPublicKeys, cfg.PluginSettings.SignaturePublicKeyFiles)
|
||||
assert.Equal(t, oldPublicKeys, th.App.Config().PluginSettings.SignaturePublicKeyFiles)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -224,23 +228,40 @@ func TestUpdateConfigRestrictSystemAdmin(t *testing.T) {
|
||||
defer th.TearDown()
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
||||
|
||||
originalCfg, resp := th.SystemAdminClient.GetConfig()
|
||||
CheckNoError(t, resp)
|
||||
t.Run("Restrict flag should be honored for sysadmin", func(t *testing.T) {
|
||||
originalCfg, resp := th.SystemAdminClient.GetConfig()
|
||||
CheckNoError(t, resp)
|
||||
|
||||
cfg := originalCfg.Clone()
|
||||
*cfg.TeamSettings.SiteName = "MyFancyName" // Allowed
|
||||
*cfg.ServiceSettings.SiteURL = "http://example.com" // Ignored
|
||||
cfg := originalCfg.Clone()
|
||||
*cfg.TeamSettings.SiteName = "MyFancyName" // Allowed
|
||||
*cfg.ServiceSettings.SiteURL = "http://example.com" // Ignored
|
||||
|
||||
returnedCfg, resp := th.SystemAdminClient.UpdateConfig(cfg)
|
||||
CheckNoError(t, resp)
|
||||
returnedCfg, resp := th.SystemAdminClient.UpdateConfig(cfg)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
require.Equal(t, "MyFancyName", *returnedCfg.TeamSettings.SiteName)
|
||||
require.Equal(t, *originalCfg.ServiceSettings.SiteURL, *returnedCfg.ServiceSettings.SiteURL)
|
||||
require.Equal(t, "MyFancyName", *returnedCfg.TeamSettings.SiteName)
|
||||
require.Equal(t, *originalCfg.ServiceSettings.SiteURL, *returnedCfg.ServiceSettings.SiteURL)
|
||||
|
||||
actualCfg, resp := th.SystemAdminClient.GetConfig()
|
||||
CheckNoError(t, resp)
|
||||
actualCfg, resp := th.SystemAdminClient.GetConfig()
|
||||
CheckNoError(t, resp)
|
||||
|
||||
require.Equal(t, returnedCfg, actualCfg)
|
||||
require.Equal(t, returnedCfg, actualCfg)
|
||||
})
|
||||
|
||||
t.Run("Restrict flag should be ignored by local mode", func(t *testing.T) {
|
||||
originalCfg, resp := th.LocalClient.GetConfig()
|
||||
CheckNoError(t, resp)
|
||||
|
||||
cfg := originalCfg.Clone()
|
||||
*cfg.TeamSettings.SiteName = "MyFancyName" // Allowed
|
||||
*cfg.ServiceSettings.SiteURL = "http://example.com" // Ignored
|
||||
|
||||
returnedCfg, resp := th.LocalClient.UpdateConfig(cfg)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
require.Equal(t, "MyFancyName", *returnedCfg.TeamSettings.SiteName)
|
||||
require.Equal(t, "http://example.com", *returnedCfg.ServiceSettings.SiteURL)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetEnvironmentConfig(t *testing.T) {
|
||||
@@ -360,19 +381,18 @@ func TestGetOldClientConfig(t *testing.T) {
|
||||
func TestPatchConfig(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
client := th.Client
|
||||
|
||||
t.Run("config is missing", func(t *testing.T) {
|
||||
_, response := client.PatchConfig(nil)
|
||||
_, response := th.Client.PatchConfig(nil)
|
||||
CheckBadRequestStatus(t, response)
|
||||
})
|
||||
|
||||
t.Run("user is not system admin", func(t *testing.T) {
|
||||
_, response := client.PatchConfig(&model.Config{})
|
||||
_, response := th.Client.PatchConfig(&model.Config{})
|
||||
CheckForbiddenStatus(t, response)
|
||||
})
|
||||
|
||||
t.Run("should not update the restricted fields when restrict toggle is on", func(t *testing.T) {
|
||||
t.Run("should not update the restricted fields when restrict toggle is on for sysadmin", func(t *testing.T) {
|
||||
*th.App.Config().ExperimentalSettings.RestrictSystemAdmin = true
|
||||
|
||||
config := model.Config{LogSettings: model.LogSettings{
|
||||
@@ -384,72 +404,94 @@ func TestPatchConfig(t *testing.T) {
|
||||
assert.Equal(t, "DEBUG", *updatedConfig.LogSettings.ConsoleLevel)
|
||||
})
|
||||
|
||||
t.Run("check if config is valid", func(t *testing.T) {
|
||||
config := model.Config{PasswordSettings: model.PasswordSettings{
|
||||
MinimumLength: model.NewInt(4),
|
||||
}}
|
||||
t.Run("should not bypass the restrict toggle if local client", func(t *testing.T) {
|
||||
*th.App.Config().ExperimentalSettings.RestrictSystemAdmin = true
|
||||
|
||||
_, response := th.SystemAdminClient.PatchConfig(&config)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, response.StatusCode)
|
||||
assert.NotNil(t, response.Error)
|
||||
assert.Equal(t, "model.config.is_valid.password_length.app_error", response.Error.Id)
|
||||
})
|
||||
|
||||
t.Run("should patch the config", func(t *testing.T) {
|
||||
*th.App.Config().ExperimentalSettings.RestrictSystemAdmin = false
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.ExperimentalDefaultChannels = []string{"some-channel"} })
|
||||
|
||||
oldConfig, _ := th.SystemAdminClient.GetConfig()
|
||||
|
||||
assert.False(t, *oldConfig.PasswordSettings.Lowercase)
|
||||
assert.NotEqual(t, 15, *oldConfig.PasswordSettings.MinimumLength)
|
||||
assert.Equal(t, "DEBUG", *oldConfig.LogSettings.ConsoleLevel)
|
||||
assert.True(t, oldConfig.PluginSettings.PluginStates["com.mattermost.nps"].Enable)
|
||||
|
||||
states := make(map[string]*model.PluginState)
|
||||
states["com.mattermost.nps"] = &model.PluginState{Enable: *model.NewBool(false)}
|
||||
config := model.Config{PasswordSettings: model.PasswordSettings{
|
||||
Lowercase: model.NewBool(true),
|
||||
MinimumLength: model.NewInt(15),
|
||||
}, LogSettings: model.LogSettings{
|
||||
config := model.Config{LogSettings: model.LogSettings{
|
||||
ConsoleLevel: model.NewString("INFO"),
|
||||
},
|
||||
TeamSettings: model.TeamSettings{
|
||||
ExperimentalDefaultChannels: []string{"another-channel"},
|
||||
},
|
||||
PluginSettings: model.PluginSettings{
|
||||
PluginStates: states,
|
||||
},
|
||||
}
|
||||
}}
|
||||
|
||||
_, response := th.SystemAdminClient.PatchConfig(&config)
|
||||
oldConfig, _ := th.LocalClient.GetConfig()
|
||||
updatedConfig, _ := th.LocalClient.PatchConfig(&config)
|
||||
|
||||
updatedConfig, _ := th.SystemAdminClient.GetConfig()
|
||||
assert.True(t, *updatedConfig.PasswordSettings.Lowercase)
|
||||
assert.Equal(t, "INFO", *updatedConfig.LogSettings.ConsoleLevel)
|
||||
assert.Equal(t, []string{"another-channel"}, updatedConfig.TeamSettings.ExperimentalDefaultChannels)
|
||||
assert.False(t, updatedConfig.PluginSettings.PluginStates["com.mattermost.nps"].Enable)
|
||||
assert.Equal(t, "no-cache, no-store, must-revalidate", response.Header.Get("Cache-Control"))
|
||||
// reset the config
|
||||
_, resp := th.LocalClient.UpdateConfig(oldConfig)
|
||||
CheckNoError(t, resp)
|
||||
})
|
||||
|
||||
t.Run("should sanitize config", func(t *testing.T) {
|
||||
config := model.Config{PasswordSettings: model.PasswordSettings{
|
||||
Symbol: model.NewBool(true),
|
||||
}}
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
t.Run("check if config is valid", func(t *testing.T) {
|
||||
config := model.Config{PasswordSettings: model.PasswordSettings{
|
||||
MinimumLength: model.NewInt(4),
|
||||
}}
|
||||
|
||||
updatedConfig, _ := th.SystemAdminClient.PatchConfig(&config)
|
||||
_, response := client.PatchConfig(&config)
|
||||
|
||||
assert.Equal(t, model.FAKE_SETTING, *updatedConfig.SqlSettings.DataSource)
|
||||
})
|
||||
assert.Equal(t, http.StatusBadRequest, response.StatusCode)
|
||||
assert.NotNil(t, response.Error)
|
||||
assert.Equal(t, "model.config.is_valid.password_length.app_error", response.Error.Id)
|
||||
})
|
||||
|
||||
t.Run("not allowing to toggle enable uploads for plugin via api", func(t *testing.T) {
|
||||
config := model.Config{PluginSettings: model.PluginSettings{
|
||||
EnableUploads: model.NewBool(true),
|
||||
}}
|
||||
t.Run("should patch the config", func(t *testing.T) {
|
||||
*th.App.Config().ExperimentalSettings.RestrictSystemAdmin = false
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.ExperimentalDefaultChannels = []string{"some-channel"} })
|
||||
|
||||
updatedConfig, _ := th.SystemAdminClient.PatchConfig(&config)
|
||||
oldConfig, _ := client.GetConfig()
|
||||
|
||||
assert.Equal(t, false, *updatedConfig.PluginSettings.EnableUploads)
|
||||
assert.False(t, *oldConfig.PasswordSettings.Lowercase)
|
||||
assert.NotEqual(t, 15, *oldConfig.PasswordSettings.MinimumLength)
|
||||
assert.Equal(t, "DEBUG", *oldConfig.LogSettings.ConsoleLevel)
|
||||
assert.True(t, oldConfig.PluginSettings.PluginStates["com.mattermost.nps"].Enable)
|
||||
|
||||
states := make(map[string]*model.PluginState)
|
||||
states["com.mattermost.nps"] = &model.PluginState{Enable: *model.NewBool(false)}
|
||||
config := model.Config{PasswordSettings: model.PasswordSettings{
|
||||
Lowercase: model.NewBool(true),
|
||||
MinimumLength: model.NewInt(15),
|
||||
}, LogSettings: model.LogSettings{
|
||||
ConsoleLevel: model.NewString("INFO"),
|
||||
},
|
||||
TeamSettings: model.TeamSettings{
|
||||
ExperimentalDefaultChannels: []string{"another-channel"},
|
||||
},
|
||||
PluginSettings: model.PluginSettings{
|
||||
PluginStates: states,
|
||||
},
|
||||
}
|
||||
|
||||
_, response := client.PatchConfig(&config)
|
||||
|
||||
updatedConfig, _ := client.GetConfig()
|
||||
assert.True(t, *updatedConfig.PasswordSettings.Lowercase)
|
||||
assert.Equal(t, "INFO", *updatedConfig.LogSettings.ConsoleLevel)
|
||||
assert.Equal(t, []string{"another-channel"}, updatedConfig.TeamSettings.ExperimentalDefaultChannels)
|
||||
assert.False(t, updatedConfig.PluginSettings.PluginStates["com.mattermost.nps"].Enable)
|
||||
assert.Equal(t, "no-cache, no-store, must-revalidate", response.Header.Get("Cache-Control"))
|
||||
|
||||
// reset the config
|
||||
_, resp := client.UpdateConfig(oldConfig)
|
||||
CheckNoError(t, resp)
|
||||
})
|
||||
|
||||
t.Run("should sanitize config", func(t *testing.T) {
|
||||
config := model.Config{PasswordSettings: model.PasswordSettings{
|
||||
Symbol: model.NewBool(true),
|
||||
}}
|
||||
|
||||
updatedConfig, _ := client.PatchConfig(&config)
|
||||
|
||||
assert.Equal(t, model.FAKE_SETTING, *updatedConfig.SqlSettings.DataSource)
|
||||
})
|
||||
|
||||
t.Run("not allowing to toggle enable uploads for plugin via api", func(t *testing.T) {
|
||||
config := model.Config{PluginSettings: model.PluginSettings{
|
||||
EnableUploads: model.NewBool(true),
|
||||
}}
|
||||
|
||||
updatedConfig, _ := client.PatchConfig(&config)
|
||||
|
||||
assert.Equal(t, false, *updatedConfig.PluginSettings.EnableUploads)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ func (api *API) ApiHandler(h func(*Context, http.ResponseWriter, *http.Request))
|
||||
TrustRequester: false,
|
||||
RequireMfa: false,
|
||||
IsStatic: false,
|
||||
IsLocal: false,
|
||||
}
|
||||
if *api.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||
return gziphandler.GzipHandler(handler)
|
||||
@@ -41,6 +42,7 @@ func (api *API) ApiSessionRequired(h func(*Context, http.ResponseWriter, *http.R
|
||||
TrustRequester: false,
|
||||
RequireMfa: true,
|
||||
IsStatic: false,
|
||||
IsLocal: false,
|
||||
}
|
||||
if *api.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||
return gziphandler.GzipHandler(handler)
|
||||
@@ -61,6 +63,7 @@ func (api *API) ApiSessionRequiredMfa(h func(*Context, http.ResponseWriter, *htt
|
||||
TrustRequester: false,
|
||||
RequireMfa: false,
|
||||
IsStatic: false,
|
||||
IsLocal: false,
|
||||
}
|
||||
if *api.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||
return gziphandler.GzipHandler(handler)
|
||||
@@ -81,6 +84,7 @@ func (api *API) ApiHandlerTrustRequester(h func(*Context, http.ResponseWriter, *
|
||||
TrustRequester: true,
|
||||
RequireMfa: false,
|
||||
IsStatic: false,
|
||||
IsLocal: false,
|
||||
}
|
||||
if *api.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||
return gziphandler.GzipHandler(handler)
|
||||
@@ -100,6 +104,7 @@ func (api *API) ApiSessionRequiredTrustRequester(h func(*Context, http.ResponseW
|
||||
TrustRequester: true,
|
||||
RequireMfa: true,
|
||||
IsStatic: false,
|
||||
IsLocal: false,
|
||||
}
|
||||
if *api.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||
return gziphandler.GzipHandler(handler)
|
||||
@@ -119,6 +124,7 @@ func (api *API) ApiSessionRequiredDisableWhenBusy(h func(*Context, http.Response
|
||||
TrustRequester: false,
|
||||
RequireMfa: false,
|
||||
IsStatic: false,
|
||||
IsLocal: false,
|
||||
DisableWhenBusy: true,
|
||||
}
|
||||
if *api.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||
@@ -127,3 +133,25 @@ func (api *API) ApiSessionRequiredDisableWhenBusy(h func(*Context, http.Response
|
||||
return handler
|
||||
|
||||
}
|
||||
|
||||
// ApiLocal provides a handler for API endpoints to be used in local
|
||||
// mode, this is, through a UNIX socket and without an authenticated
|
||||
// session, but with one that has no user set and no permission
|
||||
// restrictions
|
||||
func (api *API) ApiLocal(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
||||
handler := &web.Handler{
|
||||
GetGlobalAppOptions: api.GetGlobalAppOptions,
|
||||
HandleFunc: h,
|
||||
HandlerName: web.GetHandlerName(h),
|
||||
RequireSession: false,
|
||||
TrustRequester: false,
|
||||
RequireMfa: false,
|
||||
IsStatic: false,
|
||||
IsLocal: true,
|
||||
}
|
||||
|
||||
if *api.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||
return gziphandler.GzipHandler(handler)
|
||||
}
|
||||
return handler
|
||||
}
|
||||
|
||||
95
api4/license_local.go
Normal file
95
api4/license_local.go
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/audit"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
)
|
||||
|
||||
func (api *API) InitLicenseLocal() {
|
||||
api.BaseRoutes.ApiRoot.Handle("/license", api.ApiLocal(localAddLicense)).Methods("POST")
|
||||
api.BaseRoutes.ApiRoot.Handle("/license", api.ApiLocal(localRemoveLicense)).Methods("DELETE")
|
||||
}
|
||||
|
||||
func localAddLicense(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
auditRec := c.MakeAuditRecord("localAddLicense", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
c.LogAudit("attempt")
|
||||
|
||||
err := r.ParseMultipartForm(*c.App.Config().FileSettings.MaxFileSize)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
m := r.MultipartForm
|
||||
|
||||
fileArray, ok := m.File["license"]
|
||||
if !ok {
|
||||
c.Err = model.NewAppError("addLicense", "api.license.add_license.no_file.app_error", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if len(fileArray) <= 0 {
|
||||
c.Err = model.NewAppError("addLicense", "api.license.add_license.array.app_error", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
fileData := fileArray[0]
|
||||
auditRec.AddMeta("filename", fileData.Filename)
|
||||
|
||||
file, err := fileData.Open()
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("addLicense", "api.license.add_license.open.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
io.Copy(buf, file)
|
||||
|
||||
license, appErr := c.App.SaveLicense(buf.Bytes())
|
||||
if appErr != nil {
|
||||
if appErr.Id == model.EXPIRED_LICENSE_ERROR {
|
||||
c.LogAudit("failed - expired or non-started license")
|
||||
} else if appErr.Id == model.INVALID_LICENSE_ERROR {
|
||||
c.LogAudit("failed - invalid license")
|
||||
} else {
|
||||
c.LogAudit("failed - unable to save license")
|
||||
}
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
if *c.App.Config().JobSettings.RunJobs {
|
||||
c.App.Srv().Jobs.Workers = c.App.Srv().Jobs.InitWorkers()
|
||||
c.App.Srv().Jobs.StartWorkers()
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
c.LogAudit("success")
|
||||
|
||||
w.Write([]byte(license.ToJson()))
|
||||
}
|
||||
|
||||
func localRemoveLicense(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
auditRec := c.MakeAuditRecord("localRemoveLicense", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
c.LogAudit("attempt")
|
||||
|
||||
if err := c.App.RemoveLicense(); err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
c.LogAudit("success")
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
@@ -47,6 +47,7 @@ func TestUploadLicenseFile(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
Client := th.Client
|
||||
LocalClient := th.LocalClient
|
||||
|
||||
t.Run("as system user", func(t *testing.T) {
|
||||
ok, resp := Client.UploadLicenseFile([]byte{})
|
||||
@@ -54,11 +55,11 @@ func TestUploadLicenseFile(t *testing.T) {
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
t.Run("as system admin user", func(t *testing.T) {
|
||||
ok, resp := th.SystemAdminClient.UploadLicenseFile([]byte{})
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
|
||||
ok, resp := c.UploadLicenseFile([]byte{})
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.False(t, ok)
|
||||
})
|
||||
}, "as system admin user")
|
||||
|
||||
t.Run("as restricted system admin user", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
||||
@@ -67,12 +68,20 @@ func TestUploadLicenseFile(t *testing.T) {
|
||||
CheckForbiddenStatus(t, resp)
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
t.Run("restricted admin setting not honoured through local client", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
||||
ok, resp := LocalClient.UploadLicenseFile([]byte{})
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.False(t, ok)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRemoveLicenseFile(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
Client := th.Client
|
||||
LocalClient := th.LocalClient
|
||||
|
||||
t.Run("as system user", func(t *testing.T) {
|
||||
ok, resp := Client.RemoveLicenseFile()
|
||||
@@ -80,11 +89,11 @@ func TestRemoveLicenseFile(t *testing.T) {
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
t.Run("as system admin user", func(t *testing.T) {
|
||||
ok, resp := th.SystemAdminClient.RemoveLicenseFile()
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
|
||||
ok, resp := c.RemoveLicenseFile()
|
||||
CheckNoError(t, resp)
|
||||
require.True(t, ok)
|
||||
})
|
||||
}, "as system admin user")
|
||||
|
||||
t.Run("as restricted system admin user", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
||||
@@ -93,4 +102,12 @@ func TestRemoveLicenseFile(t *testing.T) {
|
||||
CheckForbiddenStatus(t, resp)
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
t.Run("restricted admin setting not honoured through local client", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
||||
|
||||
ok, resp := LocalClient.RemoveLicenseFile()
|
||||
CheckNoError(t, resp)
|
||||
require.True(t, ok)
|
||||
})
|
||||
}
|
||||
|
||||
13
api4/plugin_local.go
Normal file
13
api4/plugin_local.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
func (api *API) InitPluginLocal() {
|
||||
api.BaseRoutes.Plugins.Handle("", api.ApiLocal(uploadPlugin)).Methods("POST")
|
||||
api.BaseRoutes.Plugins.Handle("", api.ApiLocal(getPlugins)).Methods("GET")
|
||||
api.BaseRoutes.Plugins.Handle("/install_from_url", api.ApiLocal(installPluginFromUrl)).Methods("POST")
|
||||
api.BaseRoutes.Plugin.Handle("", api.ApiLocal(removePlugin)).Methods("DELETE")
|
||||
api.BaseRoutes.Plugin.Handle("/enable", api.ApiLocal(enablePlugin)).Methods("POST")
|
||||
api.BaseRoutes.Plugin.Handle("/disable", api.ApiLocal(disablePlugin)).Methods("POST")
|
||||
}
|
||||
@@ -33,241 +33,243 @@ func TestPlugin(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
statesJson, err := json.Marshal(th.App.Config().PluginSettings.PluginStates)
|
||||
require.Nil(t, err)
|
||||
states := map[string]*model.PluginState{}
|
||||
json.Unmarshal(statesJson, &states)
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.PluginSettings.Enable = true
|
||||
*cfg.PluginSettings.EnableUploads = true
|
||||
*cfg.PluginSettings.AllowInsecureDownloadUrl = true
|
||||
})
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
statesJson, err := json.Marshal(th.App.Config().PluginSettings.PluginStates)
|
||||
require.Nil(t, err)
|
||||
states := map[string]*model.PluginState{}
|
||||
json.Unmarshal(statesJson, &states)
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.PluginSettings.Enable = true
|
||||
*cfg.PluginSettings.EnableUploads = true
|
||||
*cfg.PluginSettings.AllowInsecureDownloadUrl = true
|
||||
})
|
||||
|
||||
path, _ := fileutils.FindDir("tests")
|
||||
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
|
||||
require.NoError(t, err)
|
||||
path, _ := fileutils.FindDir("tests")
|
||||
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Install from URL
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||
res.WriteHeader(http.StatusOK)
|
||||
res.Write(tarData)
|
||||
}))
|
||||
defer func() { testServer.Close() }()
|
||||
|
||||
url := testServer.URL
|
||||
|
||||
manifest, resp := th.SystemAdminClient.InstallPluginFromUrl(url, false)
|
||||
CheckNoError(t, resp)
|
||||
assert.Equal(t, "testplugin", manifest.Id)
|
||||
|
||||
_, resp = th.SystemAdminClient.InstallPluginFromUrl(url, false)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
manifest, resp = th.SystemAdminClient.InstallPluginFromUrl(url, true)
|
||||
CheckNoError(t, resp)
|
||||
assert.Equal(t, "testplugin", manifest.Id)
|
||||
|
||||
// Stored in File Store: Install Plugin from URL case
|
||||
pluginStored, err := th.App.FileExists("./plugins/" + manifest.Id + ".tar.gz")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, pluginStored)
|
||||
|
||||
ok, resp := th.SystemAdminClient.RemovePlugin(manifest.Id)
|
||||
CheckNoError(t, resp)
|
||||
require.True(t, ok)
|
||||
|
||||
t.Run("install plugin from URL with slow response time", func(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test to install plugin from a slow response server")
|
||||
}
|
||||
|
||||
// Install from URL - slow server to simulate longer bundle download times
|
||||
slowTestServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||
time.Sleep(60 * time.Second) // Wait longer than the previous default 30 seconds timeout
|
||||
// Install from URL
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||
res.WriteHeader(http.StatusOK)
|
||||
res.Write(tarData)
|
||||
}))
|
||||
defer func() { slowTestServer.Close() }()
|
||||
defer func() { testServer.Close() }()
|
||||
|
||||
manifest, resp = th.SystemAdminClient.InstallPluginFromUrl(slowTestServer.URL, true)
|
||||
url := testServer.URL
|
||||
|
||||
manifest, resp := client.InstallPluginFromUrl(url, false)
|
||||
CheckNoError(t, resp)
|
||||
assert.Equal(t, "testplugin", manifest.Id)
|
||||
|
||||
_, resp = client.InstallPluginFromUrl(url, false)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
manifest, resp = client.InstallPluginFromUrl(url, true)
|
||||
CheckNoError(t, resp)
|
||||
assert.Equal(t, "testplugin", manifest.Id)
|
||||
|
||||
// Stored in File Store: Install Plugin from URL case
|
||||
pluginStored, err := th.App.FileExists("./plugins/" + manifest.Id + ".tar.gz")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, pluginStored)
|
||||
|
||||
ok, resp := client.RemovePlugin(manifest.Id)
|
||||
CheckNoError(t, resp)
|
||||
require.True(t, ok)
|
||||
|
||||
t.Run("install plugin from URL with slow response time", func(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test to install plugin from a slow response server")
|
||||
}
|
||||
|
||||
// Install from URL - slow server to simulate longer bundle download times
|
||||
slowTestServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||
time.Sleep(60 * time.Second) // Wait longer than the previous default 30 seconds timeout
|
||||
res.WriteHeader(http.StatusOK)
|
||||
res.Write(tarData)
|
||||
}))
|
||||
defer func() { slowTestServer.Close() }()
|
||||
|
||||
manifest, resp = client.InstallPluginFromUrl(slowTestServer.URL, true)
|
||||
CheckNoError(t, resp)
|
||||
assert.Equal(t, "testplugin", manifest.Id)
|
||||
})
|
||||
|
||||
th.App.RemovePlugin(manifest.Id)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false })
|
||||
|
||||
_, resp = client.InstallPluginFromUrl(url, false)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true })
|
||||
|
||||
_, resp = th.Client.InstallPluginFromUrl(url, false)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
_, resp = client.InstallPluginFromUrl("http://nodata", false)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.AllowInsecureDownloadUrl = false })
|
||||
|
||||
_, resp = client.InstallPluginFromUrl(url, false)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
// Successful upload
|
||||
manifest, resp = client.UploadPlugin(bytes.NewReader(tarData))
|
||||
CheckNoError(t, resp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableUploads = true })
|
||||
|
||||
manifest, resp = client.UploadPluginForced(bytes.NewReader(tarData))
|
||||
defer os.RemoveAll("plugins/testplugin")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
assert.Equal(t, "testplugin", manifest.Id)
|
||||
|
||||
// Stored in File Store: Upload Plugin case
|
||||
pluginStored, err = th.App.FileExists("./plugins/" + manifest.Id + ".tar.gz")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, pluginStored)
|
||||
|
||||
// Upload error cases
|
||||
_, resp = client.UploadPlugin(bytes.NewReader([]byte("badfile")))
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false })
|
||||
_, resp = client.UploadPlugin(bytes.NewReader(tarData))
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.PluginSettings.Enable = true
|
||||
*cfg.PluginSettings.EnableUploads = false
|
||||
})
|
||||
_, resp = client.UploadPlugin(bytes.NewReader(tarData))
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
|
||||
_, resp = client.InstallPluginFromUrl(url, false)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableUploads = true })
|
||||
_, resp = th.Client.UploadPlugin(bytes.NewReader(tarData))
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
// Successful gets
|
||||
pluginsResp, resp := client.GetPlugins()
|
||||
CheckNoError(t, resp)
|
||||
|
||||
found := false
|
||||
for _, m := range pluginsResp.Inactive {
|
||||
if m.Id == manifest.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, found)
|
||||
|
||||
found = false
|
||||
for _, m := range pluginsResp.Active {
|
||||
if m.Id == manifest.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
assert.False(t, found)
|
||||
|
||||
// Successful activate
|
||||
ok, resp = client.EnablePlugin(manifest.Id)
|
||||
CheckNoError(t, resp)
|
||||
assert.True(t, ok)
|
||||
|
||||
pluginsResp, resp = client.GetPlugins()
|
||||
CheckNoError(t, resp)
|
||||
|
||||
found = false
|
||||
for _, m := range pluginsResp.Active {
|
||||
if m.Id == manifest.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, found)
|
||||
|
||||
// Activate error case
|
||||
ok, resp = client.EnablePlugin("junk")
|
||||
CheckNotFoundStatus(t, resp)
|
||||
assert.False(t, ok)
|
||||
|
||||
ok, resp = client.EnablePlugin("JUNK")
|
||||
CheckNotFoundStatus(t, resp)
|
||||
assert.False(t, ok)
|
||||
|
||||
// Successful deactivate
|
||||
ok, resp = client.DisablePlugin(manifest.Id)
|
||||
CheckNoError(t, resp)
|
||||
assert.True(t, ok)
|
||||
|
||||
pluginsResp, resp = client.GetPlugins()
|
||||
CheckNoError(t, resp)
|
||||
|
||||
found = false
|
||||
for _, m := range pluginsResp.Inactive {
|
||||
if m.Id == manifest.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, found)
|
||||
|
||||
// Deactivate error case
|
||||
ok, resp = client.DisablePlugin("junk")
|
||||
CheckNotFoundStatus(t, resp)
|
||||
assert.False(t, ok)
|
||||
|
||||
// Get error cases
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false })
|
||||
_, resp = client.GetPlugins()
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true })
|
||||
_, resp = th.Client.GetPlugins()
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
// Successful webapp get
|
||||
_, resp = client.EnablePlugin(manifest.Id)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
manifests, resp := th.Client.GetWebappPlugins()
|
||||
CheckNoError(t, resp)
|
||||
|
||||
found = false
|
||||
for _, m := range manifests {
|
||||
if m.Id == manifest.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, found)
|
||||
|
||||
// Successful remove
|
||||
ok, resp = client.RemovePlugin(manifest.Id)
|
||||
CheckNoError(t, resp)
|
||||
assert.True(t, ok)
|
||||
|
||||
// Remove error cases
|
||||
ok, resp = client.RemovePlugin(manifest.Id)
|
||||
CheckNotFoundStatus(t, resp)
|
||||
assert.False(t, ok)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false })
|
||||
_, resp = client.RemovePlugin(manifest.Id)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true })
|
||||
_, resp = th.Client.RemovePlugin(manifest.Id)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
_, resp = client.RemovePlugin("bad.id")
|
||||
CheckNotFoundStatus(t, resp)
|
||||
})
|
||||
|
||||
th.App.RemovePlugin(manifest.Id)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false })
|
||||
|
||||
_, resp = th.SystemAdminClient.InstallPluginFromUrl(url, false)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true })
|
||||
|
||||
_, resp = th.Client.InstallPluginFromUrl(url, false)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
_, resp = th.SystemAdminClient.InstallPluginFromUrl("http://nodata", false)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.AllowInsecureDownloadUrl = false })
|
||||
|
||||
_, resp = th.SystemAdminClient.InstallPluginFromUrl(url, false)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
// Successful upload
|
||||
manifest, resp = th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
|
||||
CheckNoError(t, resp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableUploads = true })
|
||||
|
||||
manifest, resp = th.SystemAdminClient.UploadPluginForced(bytes.NewReader(tarData))
|
||||
defer os.RemoveAll("plugins/testplugin")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
assert.Equal(t, "testplugin", manifest.Id)
|
||||
|
||||
// Stored in File Store: Upload Plugin case
|
||||
pluginStored, err = th.App.FileExists("./plugins/" + manifest.Id + ".tar.gz")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, pluginStored)
|
||||
|
||||
// Upload error cases
|
||||
_, resp = th.SystemAdminClient.UploadPlugin(bytes.NewReader([]byte("badfile")))
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false })
|
||||
_, resp = th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.PluginSettings.Enable = true
|
||||
*cfg.PluginSettings.EnableUploads = false
|
||||
})
|
||||
_, resp = th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
|
||||
_, resp = th.SystemAdminClient.InstallPluginFromUrl(url, false)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableUploads = true })
|
||||
_, resp = th.Client.UploadPlugin(bytes.NewReader(tarData))
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
// Successful gets
|
||||
pluginsResp, resp := th.SystemAdminClient.GetPlugins()
|
||||
CheckNoError(t, resp)
|
||||
|
||||
found := false
|
||||
for _, m := range pluginsResp.Inactive {
|
||||
if m.Id == manifest.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, found)
|
||||
|
||||
found = false
|
||||
for _, m := range pluginsResp.Active {
|
||||
if m.Id == manifest.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
assert.False(t, found)
|
||||
|
||||
// Successful activate
|
||||
ok, resp = th.SystemAdminClient.EnablePlugin(manifest.Id)
|
||||
CheckNoError(t, resp)
|
||||
assert.True(t, ok)
|
||||
|
||||
pluginsResp, resp = th.SystemAdminClient.GetPlugins()
|
||||
CheckNoError(t, resp)
|
||||
|
||||
found = false
|
||||
for _, m := range pluginsResp.Active {
|
||||
if m.Id == manifest.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, found)
|
||||
|
||||
// Activate error case
|
||||
ok, resp = th.SystemAdminClient.EnablePlugin("junk")
|
||||
CheckNotFoundStatus(t, resp)
|
||||
assert.False(t, ok)
|
||||
|
||||
ok, resp = th.SystemAdminClient.EnablePlugin("JUNK")
|
||||
CheckNotFoundStatus(t, resp)
|
||||
assert.False(t, ok)
|
||||
|
||||
// Successful deactivate
|
||||
ok, resp = th.SystemAdminClient.DisablePlugin(manifest.Id)
|
||||
CheckNoError(t, resp)
|
||||
assert.True(t, ok)
|
||||
|
||||
pluginsResp, resp = th.SystemAdminClient.GetPlugins()
|
||||
CheckNoError(t, resp)
|
||||
|
||||
found = false
|
||||
for _, m := range pluginsResp.Inactive {
|
||||
if m.Id == manifest.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, found)
|
||||
|
||||
// Deactivate error case
|
||||
ok, resp = th.SystemAdminClient.DisablePlugin("junk")
|
||||
CheckNotFoundStatus(t, resp)
|
||||
assert.False(t, ok)
|
||||
|
||||
// Get error cases
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false })
|
||||
_, resp = th.SystemAdminClient.GetPlugins()
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true })
|
||||
_, resp = th.Client.GetPlugins()
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
// Successful webapp get
|
||||
_, resp = th.SystemAdminClient.EnablePlugin(manifest.Id)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
manifests, resp := th.Client.GetWebappPlugins()
|
||||
CheckNoError(t, resp)
|
||||
|
||||
found = false
|
||||
for _, m := range manifests {
|
||||
if m.Id == manifest.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, found)
|
||||
|
||||
// Successful remove
|
||||
ok, resp = th.SystemAdminClient.RemovePlugin(manifest.Id)
|
||||
CheckNoError(t, resp)
|
||||
assert.True(t, ok)
|
||||
|
||||
// Remove error cases
|
||||
ok, resp = th.SystemAdminClient.RemovePlugin(manifest.Id)
|
||||
CheckNotFoundStatus(t, resp)
|
||||
assert.False(t, ok)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false })
|
||||
_, resp = th.SystemAdminClient.RemovePlugin(manifest.Id)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true })
|
||||
_, resp = th.Client.RemovePlugin(manifest.Id)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
_, resp = th.SystemAdminClient.RemovePlugin("bad.id")
|
||||
CheckNotFoundStatus(t, resp)
|
||||
}
|
||||
|
||||
func TestNotifyClusterPluginEvent(t *testing.T) {
|
||||
@@ -387,80 +389,82 @@ func TestDisableOnRemove(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.PluginSettings.Enable = true
|
||||
*cfg.PluginSettings.EnableUploads = true
|
||||
})
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.PluginSettings.Enable = true
|
||||
*cfg.PluginSettings.EnableUploads = true
|
||||
})
|
||||
|
||||
// Upload
|
||||
manifest, resp := th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
|
||||
CheckNoError(t, resp)
|
||||
require.Equal(t, "testplugin", manifest.Id)
|
||||
|
||||
// Check initial status
|
||||
pluginsResp, resp := th.SystemAdminClient.GetPlugins()
|
||||
CheckNoError(t, resp)
|
||||
require.Empty(t, pluginsResp.Active)
|
||||
require.Equal(t, pluginsResp.Inactive, []*model.PluginInfo{{
|
||||
Manifest: *manifest,
|
||||
}})
|
||||
|
||||
// Enable plugin
|
||||
ok, resp := th.SystemAdminClient.EnablePlugin(manifest.Id)
|
||||
CheckNoError(t, resp)
|
||||
require.True(t, ok)
|
||||
|
||||
// Confirm enabled status
|
||||
pluginsResp, resp = th.SystemAdminClient.GetPlugins()
|
||||
CheckNoError(t, resp)
|
||||
require.Empty(t, pluginsResp.Inactive)
|
||||
require.Equal(t, pluginsResp.Active, []*model.PluginInfo{{
|
||||
Manifest: *manifest,
|
||||
}})
|
||||
|
||||
if tc.Upgrade {
|
||||
// Upgrade
|
||||
manifest, resp = th.SystemAdminClient.UploadPluginForced(bytes.NewReader(tarData))
|
||||
// Upload
|
||||
manifest, resp := client.UploadPlugin(bytes.NewReader(tarData))
|
||||
CheckNoError(t, resp)
|
||||
require.Equal(t, "testplugin", manifest.Id)
|
||||
|
||||
// Plugin should remain active
|
||||
pluginsResp, resp = th.SystemAdminClient.GetPlugins()
|
||||
// Check initial status
|
||||
pluginsResp, resp := client.GetPlugins()
|
||||
CheckNoError(t, resp)
|
||||
require.Empty(t, pluginsResp.Active)
|
||||
require.Equal(t, pluginsResp.Inactive, []*model.PluginInfo{{
|
||||
Manifest: *manifest,
|
||||
}})
|
||||
|
||||
// Enable plugin
|
||||
ok, resp := client.EnablePlugin(manifest.Id)
|
||||
CheckNoError(t, resp)
|
||||
require.True(t, ok)
|
||||
|
||||
// Confirm enabled status
|
||||
pluginsResp, resp = client.GetPlugins()
|
||||
CheckNoError(t, resp)
|
||||
require.Empty(t, pluginsResp.Inactive)
|
||||
require.Equal(t, pluginsResp.Active, []*model.PluginInfo{{
|
||||
Manifest: *manifest,
|
||||
}})
|
||||
}
|
||||
|
||||
// Remove plugin
|
||||
ok, resp = th.SystemAdminClient.RemovePlugin(manifest.Id)
|
||||
CheckNoError(t, resp)
|
||||
require.True(t, ok)
|
||||
if tc.Upgrade {
|
||||
// Upgrade
|
||||
manifest, resp = client.UploadPluginForced(bytes.NewReader(tarData))
|
||||
CheckNoError(t, resp)
|
||||
require.Equal(t, "testplugin", manifest.Id)
|
||||
|
||||
// Plugin should have no status
|
||||
pluginsResp, resp = th.SystemAdminClient.GetPlugins()
|
||||
CheckNoError(t, resp)
|
||||
require.Empty(t, pluginsResp.Inactive)
|
||||
require.Empty(t, pluginsResp.Active)
|
||||
// Plugin should remain active
|
||||
pluginsResp, resp = client.GetPlugins()
|
||||
CheckNoError(t, resp)
|
||||
require.Empty(t, pluginsResp.Inactive)
|
||||
require.Equal(t, pluginsResp.Active, []*model.PluginInfo{{
|
||||
Manifest: *manifest,
|
||||
}})
|
||||
}
|
||||
|
||||
// Upload same plugin
|
||||
manifest, resp = th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
|
||||
CheckNoError(t, resp)
|
||||
require.Equal(t, "testplugin", manifest.Id)
|
||||
// Remove plugin
|
||||
ok, resp = client.RemovePlugin(manifest.Id)
|
||||
CheckNoError(t, resp)
|
||||
require.True(t, ok)
|
||||
|
||||
// Plugin should be inactive
|
||||
pluginsResp, resp = th.SystemAdminClient.GetPlugins()
|
||||
CheckNoError(t, resp)
|
||||
require.Empty(t, pluginsResp.Active)
|
||||
require.Equal(t, pluginsResp.Inactive, []*model.PluginInfo{{
|
||||
Manifest: *manifest,
|
||||
}})
|
||||
// Plugin should have no status
|
||||
pluginsResp, resp = client.GetPlugins()
|
||||
CheckNoError(t, resp)
|
||||
require.Empty(t, pluginsResp.Inactive)
|
||||
require.Empty(t, pluginsResp.Active)
|
||||
|
||||
// Clean up
|
||||
ok, resp = th.SystemAdminClient.RemovePlugin(manifest.Id)
|
||||
CheckNoError(t, resp)
|
||||
require.True(t, ok)
|
||||
// Upload same plugin
|
||||
manifest, resp = client.UploadPlugin(bytes.NewReader(tarData))
|
||||
CheckNoError(t, resp)
|
||||
require.Equal(t, "testplugin", manifest.Id)
|
||||
|
||||
// Plugin should be inactive
|
||||
pluginsResp, resp = client.GetPlugins()
|
||||
CheckNoError(t, resp)
|
||||
require.Empty(t, pluginsResp.Active)
|
||||
require.Equal(t, pluginsResp.Inactive, []*model.PluginInfo{{
|
||||
Manifest: *manifest,
|
||||
}})
|
||||
|
||||
// Clean up
|
||||
ok, resp = client.RemovePlugin(manifest.Id)
|
||||
CheckNoError(t, resp)
|
||||
require.True(t, ok)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
8
api4/team_local.go
Normal file
8
api4/team_local.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
func (api *API) InitTeamLocal() {
|
||||
api.BaseRoutes.Teams.Handle("", api.ApiLocal(getAllTeams)).Methods("GET")
|
||||
}
|
||||
Reference in New Issue
Block a user