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>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
e8081b7a0f
Коммит
0d89ff5d0e
30
api4/api.go
30
api4/api.go
@@ -255,6 +255,36 @@ func Init(configservice configservice.ConfigService, globalOptionsFunc app.AppOp
|
|||||||
return api
|
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) {
|
func (api *API) Handle404(w http.ResponseWriter, r *http.Request) {
|
||||||
web.Handle404(api.ConfigService, w, r)
|
web.Handle404(api.ConfigService, w, r)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ type TestHelper struct {
|
|||||||
SystemAdminUser *model.User
|
SystemAdminUser *model.User
|
||||||
tempWorkspace string
|
tempWorkspace string
|
||||||
|
|
||||||
|
LocalClient *model.Client4
|
||||||
|
|
||||||
IncludeCacheLayer bool
|
IncludeCacheLayer bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +80,8 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent
|
|||||||
config := memoryStore.Get()
|
config := memoryStore.Get()
|
||||||
*config.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins")
|
*config.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins")
|
||||||
*config.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp")
|
*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 {
|
if updateConfig != nil {
|
||||||
updateConfig(config)
|
updateConfig(config)
|
||||||
}
|
}
|
||||||
@@ -118,13 +122,13 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent
|
|||||||
})
|
})
|
||||||
prevListenAddress := *th.App.Config().ServiceSettings.ListenAddress
|
prevListenAddress := *th.App.Config().ServiceSettings.ListenAddress
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" })
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" })
|
||||||
serverErr := th.Server.Start()
|
if err := th.Server.Start(); err != nil {
|
||||||
if serverErr != nil {
|
panic(err)
|
||||||
panic(serverErr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress })
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress })
|
||||||
Init(th.Server, th.Server.AppOptions, th.App.Srv().Router)
|
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)
|
web.New(th.Server, th.Server.AppOptions, th.App.Srv().Router)
|
||||||
wsapi.Init(th.App, th.App.Srv().WebSocketRouter)
|
wsapi.Init(th.App, th.App.Srv().WebSocketRouter)
|
||||||
th.App.DoAppMigrations()
|
th.App.DoAppMigrations()
|
||||||
@@ -149,6 +153,8 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent
|
|||||||
th.Client = th.CreateClient()
|
th.Client = th.CreateClient()
|
||||||
th.SystemAdminClient = th.CreateClient()
|
th.SystemAdminClient = th.CreateClient()
|
||||||
|
|
||||||
|
th.LocalClient = th.CreateLocalClient(*config.ServiceSettings.LocalModeSocketLocation)
|
||||||
|
|
||||||
if th.tempWorkspace == "" {
|
if th.tempWorkspace == "" {
|
||||||
th.tempWorkspace = 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))
|
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) {
|
func (me *TestHelper) CreateWebSocketClient() (*model.WebSocketClient, *model.AppError) {
|
||||||
return model.NewWebSocketClient4(fmt.Sprintf("ws://localhost:%v", me.App.Srv().ListenAddr.Port), me.Client.AuthToken)
|
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
|
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 {
|
func GenerateTestUsername() string {
|
||||||
return "fakeuser" + model.NewRandomString(10)
|
return "fakeuser" + model.NewRandomString(10)
|
||||||
}
|
}
|
||||||
|
|||||||
41
api4/channel_local.go
Обычный файл
41
api4/channel_local.go
Обычный файл
@@ -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)
|
_, resp = Client.CreateChannel(private)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
channel.Name = GenerateTestChannelName()
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||||
_, resp = th.SystemAdminClient.CreateChannel(channel)
|
channel.Name = GenerateTestChannelName()
|
||||||
CheckNoError(t, resp)
|
_, resp = client.CreateChannel(channel)
|
||||||
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
private.Name = GenerateTestChannelName()
|
private.Name = GenerateTestChannelName()
|
||||||
_, resp = th.SystemAdminClient.CreateChannel(private)
|
_, resp = client.CreateChannel(private)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
})
|
||||||
|
|
||||||
// Test posting Garbage
|
// Test posting Garbage
|
||||||
r, err := Client.DoApiPost("/channels", "garbage")
|
r, err := Client.DoApiPost("/channels", "garbage")
|
||||||
@@ -901,28 +903,30 @@ func TestGetAllChannels(t *testing.T) {
|
|||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
Client := th.Client
|
Client := th.Client
|
||||||
|
|
||||||
channels, resp := th.SystemAdminClient.GetAllChannels(0, 20, "")
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||||
CheckNoError(t, resp)
|
channels, resp := client.GetAllChannels(0, 20, "")
|
||||||
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
// At least, all the not-deleted channels created during the InitBasic
|
// At least, all the not-deleted channels created during the InitBasic
|
||||||
require.True(t, len(*channels) >= 3)
|
require.True(t, len(*channels) >= 3)
|
||||||
for _, c := range *channels {
|
for _, c := range *channels {
|
||||||
require.NotEqual(t, c.TeamId, "")
|
require.NotEqual(t, c.TeamId, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
channels, resp = th.SystemAdminClient.GetAllChannels(0, 10, "")
|
channels, resp = client.GetAllChannels(0, 10, "")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
require.True(t, len(*channels) >= 3)
|
require.True(t, len(*channels) >= 3)
|
||||||
|
|
||||||
channels, resp = th.SystemAdminClient.GetAllChannels(1, 1, "")
|
channels, resp = client.GetAllChannels(1, 1, "")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
require.Len(t, *channels, 1)
|
require.Len(t, *channels, 1)
|
||||||
|
|
||||||
channels, resp = th.SystemAdminClient.GetAllChannels(10000, 10000, "")
|
channels, resp = client.GetAllChannels(10000, 10000, "")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
require.Empty(t, *channels)
|
require.Empty(t, *channels)
|
||||||
|
})
|
||||||
|
|
||||||
_, resp = Client.GetAllChannels(0, 20, "")
|
_, resp := Client.GetAllChannels(0, 20, "")
|
||||||
CheckForbiddenStatus(t, resp)
|
CheckForbiddenStatus(t, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
112
api4/config_local.go
Обычный файл
112
api4/config_local.go
Обычный файл
@@ -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()
|
_, resp := Client.GetConfig()
|
||||||
CheckForbiddenStatus(t, resp)
|
CheckForbiddenStatus(t, resp)
|
||||||
|
|
||||||
cfg, resp := th.SystemAdminClient.GetConfig()
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||||
CheckNoError(t, resp)
|
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 {
|
if *cfg.LdapSettings.BindPassword != model.FAKE_SETTING && len(*cfg.LdapSettings.BindPassword) != 0 {
|
||||||
require.FailNow(t, "did not sanitize properly")
|
require.FailNow(t, "did not sanitize properly")
|
||||||
}
|
}
|
||||||
require.Equal(t, model.FAKE_SETTING, *cfg.FileSettings.PublicLinkSalt, "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 {
|
if *cfg.FileSettings.AmazonS3SecretAccessKey != model.FAKE_SETTING && len(*cfg.FileSettings.AmazonS3SecretAccessKey) != 0 {
|
||||||
require.FailNow(t, "did not sanitize properly")
|
require.FailNow(t, "did not sanitize properly")
|
||||||
}
|
}
|
||||||
if *cfg.EmailSettings.SMTPPassword != model.FAKE_SETTING && len(*cfg.EmailSettings.SMTPPassword) != 0 {
|
if *cfg.EmailSettings.SMTPPassword != model.FAKE_SETTING && len(*cfg.EmailSettings.SMTPPassword) != 0 {
|
||||||
require.FailNow(t, "did not sanitize properly")
|
require.FailNow(t, "did not sanitize properly")
|
||||||
}
|
}
|
||||||
if *cfg.GitLabSettings.Secret != model.FAKE_SETTING && len(*cfg.GitLabSettings.Secret) != 0 {
|
if *cfg.GitLabSettings.Secret != model.FAKE_SETTING && len(*cfg.GitLabSettings.Secret) != 0 {
|
||||||
require.FailNow(t, "did not sanitize properly")
|
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.DataSource, "did not sanitize properly")
|
||||||
require.Equal(t, model.FAKE_SETTING, *cfg.SqlSettings.AtRestEncryptKey, "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 {
|
if !strings.Contains(strings.Join(cfg.SqlSettings.DataSourceReplicas, " "), model.FAKE_SETTING) && len(cfg.SqlSettings.DataSourceReplicas) != 0 {
|
||||||
require.FailNow(t, "did not sanitize properly")
|
require.FailNow(t, "did not sanitize properly")
|
||||||
}
|
}
|
||||||
if !strings.Contains(strings.Join(cfg.SqlSettings.DataSourceSearchReplicas, " "), model.FAKE_SETTING) && len(cfg.SqlSettings.DataSourceSearchReplicas) != 0 {
|
if !strings.Contains(strings.Join(cfg.SqlSettings.DataSourceSearchReplicas, " "), model.FAKE_SETTING) && len(cfg.SqlSettings.DataSourceSearchReplicas) != 0 {
|
||||||
require.FailNow(t, "did not sanitize properly")
|
require.FailNow(t, "did not sanitize properly")
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReloadConfig(t *testing.T) {
|
func TestReloadConfig(t *testing.T) {
|
||||||
@@ -88,66 +90,68 @@ func TestUpdateConfig(t *testing.T) {
|
|||||||
_, resp = Client.UpdateConfig(cfg)
|
_, resp = Client.UpdateConfig(cfg)
|
||||||
CheckForbiddenStatus(t, resp)
|
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.TeamSettings.SiteName = "MyFancyName"
|
||||||
cfg, resp = th.SystemAdminClient.UpdateConfig(cfg)
|
cfg, resp = client.UpdateConfig(cfg)
|
||||||
CheckNoError(t, resp)
|
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
|
//Revert the change
|
||||||
badcfg := cfg.Clone()
|
cfg.TeamSettings.SiteName = SiteName
|
||||||
badcfg.PasswordSettings.MinimumLength = model.NewInt(4)
|
cfg, resp = client.UpdateConfig(cfg)
|
||||||
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)
|
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
assert.Equal(t, oldEnableUploads, *cfg.PluginSettings.EnableUploads)
|
|
||||||
assert.Equal(t, oldEnableUploads, *th.App.Config().PluginSettings.EnableUploads)
|
|
||||||
|
|
||||||
cfg.PluginSettings.EnableUploads = nil
|
require.Equal(t, SiteName, cfg.TeamSettings.SiteName, "It should update the SiteName")
|
||||||
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)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Should not be able to modify PluginSettings.SignaturePublicKeyFiles", func(t *testing.T) {
|
t.Run("Should set defaults for missing fields", func(t *testing.T) {
|
||||||
oldPublicKeys := th.App.Config().PluginSettings.SignaturePublicKeyFiles
|
_, appErr := th.SystemAdminClient.DoApiPut(th.SystemAdminClient.GetConfigRoute(), `{"ServiceSettings":{}}`)
|
||||||
cfg.PluginSettings.SignaturePublicKeyFiles = append(cfg.PluginSettings.SignaturePublicKeyFiles, "new_signature")
|
require.Nil(t, appErr)
|
||||||
|
})
|
||||||
|
|
||||||
cfg, resp = th.SystemAdminClient.UpdateConfig(cfg)
|
t.Run("Should fail with validation error if invalid config setting is passed", func(t *testing.T) {
|
||||||
CheckNoError(t, resp)
|
//Revert the change
|
||||||
assert.Equal(t, oldPublicKeys, cfg.PluginSettings.SignaturePublicKeyFiles)
|
badcfg := cfg.Clone()
|
||||||
assert.Equal(t, oldPublicKeys, th.App.Config().PluginSettings.SignaturePublicKeyFiles)
|
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
|
t.Run("Should not be able to modify PluginSettings.EnableUploads", func(t *testing.T) {
|
||||||
cfg, resp = th.SystemAdminClient.UpdateConfig(cfg)
|
oldEnableUploads := *th.App.Config().PluginSettings.EnableUploads
|
||||||
CheckNoError(t, resp)
|
*cfg.PluginSettings.EnableUploads = !oldEnableUploads
|
||||||
assert.Equal(t, oldPublicKeys, cfg.PluginSettings.SignaturePublicKeyFiles)
|
|
||||||
assert.Equal(t, oldPublicKeys, th.App.Config().PluginSettings.SignaturePublicKeyFiles)
|
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()
|
defer th.TearDown()
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
||||||
|
|
||||||
originalCfg, resp := th.SystemAdminClient.GetConfig()
|
t.Run("Restrict flag should be honored for sysadmin", func(t *testing.T) {
|
||||||
CheckNoError(t, resp)
|
originalCfg, resp := th.SystemAdminClient.GetConfig()
|
||||||
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
cfg := originalCfg.Clone()
|
cfg := originalCfg.Clone()
|
||||||
*cfg.TeamSettings.SiteName = "MyFancyName" // Allowed
|
*cfg.TeamSettings.SiteName = "MyFancyName" // Allowed
|
||||||
*cfg.ServiceSettings.SiteURL = "http://example.com" // Ignored
|
*cfg.ServiceSettings.SiteURL = "http://example.com" // Ignored
|
||||||
|
|
||||||
returnedCfg, resp := th.SystemAdminClient.UpdateConfig(cfg)
|
returnedCfg, resp := th.SystemAdminClient.UpdateConfig(cfg)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
require.Equal(t, "MyFancyName", *returnedCfg.TeamSettings.SiteName)
|
require.Equal(t, "MyFancyName", *returnedCfg.TeamSettings.SiteName)
|
||||||
require.Equal(t, *originalCfg.ServiceSettings.SiteURL, *returnedCfg.ServiceSettings.SiteURL)
|
require.Equal(t, *originalCfg.ServiceSettings.SiteURL, *returnedCfg.ServiceSettings.SiteURL)
|
||||||
|
|
||||||
actualCfg, resp := th.SystemAdminClient.GetConfig()
|
actualCfg, resp := th.SystemAdminClient.GetConfig()
|
||||||
CheckNoError(t, resp)
|
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) {
|
func TestGetEnvironmentConfig(t *testing.T) {
|
||||||
@@ -360,19 +381,18 @@ func TestGetOldClientConfig(t *testing.T) {
|
|||||||
func TestPatchConfig(t *testing.T) {
|
func TestPatchConfig(t *testing.T) {
|
||||||
th := Setup(t).InitBasic()
|
th := Setup(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
client := th.Client
|
|
||||||
|
|
||||||
t.Run("config is missing", func(t *testing.T) {
|
t.Run("config is missing", func(t *testing.T) {
|
||||||
_, response := client.PatchConfig(nil)
|
_, response := th.Client.PatchConfig(nil)
|
||||||
CheckBadRequestStatus(t, response)
|
CheckBadRequestStatus(t, response)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("user is not system admin", func(t *testing.T) {
|
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)
|
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
|
*th.App.Config().ExperimentalSettings.RestrictSystemAdmin = true
|
||||||
|
|
||||||
config := model.Config{LogSettings: model.LogSettings{
|
config := model.Config{LogSettings: model.LogSettings{
|
||||||
@@ -384,72 +404,94 @@ func TestPatchConfig(t *testing.T) {
|
|||||||
assert.Equal(t, "DEBUG", *updatedConfig.LogSettings.ConsoleLevel)
|
assert.Equal(t, "DEBUG", *updatedConfig.LogSettings.ConsoleLevel)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("check if config is valid", func(t *testing.T) {
|
t.Run("should not bypass the restrict toggle if local client", func(t *testing.T) {
|
||||||
config := model.Config{PasswordSettings: model.PasswordSettings{
|
*th.App.Config().ExperimentalSettings.RestrictSystemAdmin = true
|
||||||
MinimumLength: model.NewInt(4),
|
|
||||||
}}
|
|
||||||
|
|
||||||
_, response := th.SystemAdminClient.PatchConfig(&config)
|
config := model.Config{LogSettings: model.LogSettings{
|
||||||
|
|
||||||
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{
|
|
||||||
ConsoleLevel: model.NewString("INFO"),
|
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, "INFO", *updatedConfig.LogSettings.ConsoleLevel)
|
||||||
assert.Equal(t, []string{"another-channel"}, updatedConfig.TeamSettings.ExperimentalDefaultChannels)
|
// reset the config
|
||||||
assert.False(t, updatedConfig.PluginSettings.PluginStates["com.mattermost.nps"].Enable)
|
_, resp := th.LocalClient.UpdateConfig(oldConfig)
|
||||||
assert.Equal(t, "no-cache, no-store, must-revalidate", response.Header.Get("Cache-Control"))
|
CheckNoError(t, resp)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should sanitize config", func(t *testing.T) {
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||||
config := model.Config{PasswordSettings: model.PasswordSettings{
|
t.Run("check if config is valid", func(t *testing.T) {
|
||||||
Symbol: model.NewBool(true),
|
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) {
|
t.Run("should patch the config", func(t *testing.T) {
|
||||||
config := model.Config{PluginSettings: model.PluginSettings{
|
*th.App.Config().ExperimentalSettings.RestrictSystemAdmin = false
|
||||||
EnableUploads: model.NewBool(true),
|
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,
|
TrustRequester: false,
|
||||||
RequireMfa: false,
|
RequireMfa: false,
|
||||||
IsStatic: false,
|
IsStatic: false,
|
||||||
|
IsLocal: false,
|
||||||
}
|
}
|
||||||
if *api.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
if *api.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||||
return gziphandler.GzipHandler(handler)
|
return gziphandler.GzipHandler(handler)
|
||||||
@@ -41,6 +42,7 @@ func (api *API) ApiSessionRequired(h func(*Context, http.ResponseWriter, *http.R
|
|||||||
TrustRequester: false,
|
TrustRequester: false,
|
||||||
RequireMfa: true,
|
RequireMfa: true,
|
||||||
IsStatic: false,
|
IsStatic: false,
|
||||||
|
IsLocal: false,
|
||||||
}
|
}
|
||||||
if *api.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
if *api.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||||
return gziphandler.GzipHandler(handler)
|
return gziphandler.GzipHandler(handler)
|
||||||
@@ -61,6 +63,7 @@ func (api *API) ApiSessionRequiredMfa(h func(*Context, http.ResponseWriter, *htt
|
|||||||
TrustRequester: false,
|
TrustRequester: false,
|
||||||
RequireMfa: false,
|
RequireMfa: false,
|
||||||
IsStatic: false,
|
IsStatic: false,
|
||||||
|
IsLocal: false,
|
||||||
}
|
}
|
||||||
if *api.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
if *api.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||||
return gziphandler.GzipHandler(handler)
|
return gziphandler.GzipHandler(handler)
|
||||||
@@ -81,6 +84,7 @@ func (api *API) ApiHandlerTrustRequester(h func(*Context, http.ResponseWriter, *
|
|||||||
TrustRequester: true,
|
TrustRequester: true,
|
||||||
RequireMfa: false,
|
RequireMfa: false,
|
||||||
IsStatic: false,
|
IsStatic: false,
|
||||||
|
IsLocal: false,
|
||||||
}
|
}
|
||||||
if *api.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
if *api.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||||
return gziphandler.GzipHandler(handler)
|
return gziphandler.GzipHandler(handler)
|
||||||
@@ -100,6 +104,7 @@ func (api *API) ApiSessionRequiredTrustRequester(h func(*Context, http.ResponseW
|
|||||||
TrustRequester: true,
|
TrustRequester: true,
|
||||||
RequireMfa: true,
|
RequireMfa: true,
|
||||||
IsStatic: false,
|
IsStatic: false,
|
||||||
|
IsLocal: false,
|
||||||
}
|
}
|
||||||
if *api.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
if *api.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||||
return gziphandler.GzipHandler(handler)
|
return gziphandler.GzipHandler(handler)
|
||||||
@@ -119,6 +124,7 @@ func (api *API) ApiSessionRequiredDisableWhenBusy(h func(*Context, http.Response
|
|||||||
TrustRequester: false,
|
TrustRequester: false,
|
||||||
RequireMfa: false,
|
RequireMfa: false,
|
||||||
IsStatic: false,
|
IsStatic: false,
|
||||||
|
IsLocal: false,
|
||||||
DisableWhenBusy: true,
|
DisableWhenBusy: true,
|
||||||
}
|
}
|
||||||
if *api.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
if *api.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||||
@@ -127,3 +133,25 @@ func (api *API) ApiSessionRequiredDisableWhenBusy(h func(*Context, http.Response
|
|||||||
return handler
|
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
Обычный файл
95
api4/license_local.go
Обычный файл
@@ -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()
|
th := Setup(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
Client := th.Client
|
Client := th.Client
|
||||||
|
LocalClient := th.LocalClient
|
||||||
|
|
||||||
t.Run("as system user", func(t *testing.T) {
|
t.Run("as system user", func(t *testing.T) {
|
||||||
ok, resp := Client.UploadLicenseFile([]byte{})
|
ok, resp := Client.UploadLicenseFile([]byte{})
|
||||||
@@ -54,11 +55,11 @@ func TestUploadLicenseFile(t *testing.T) {
|
|||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("as system admin user", func(t *testing.T) {
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
|
||||||
ok, resp := th.SystemAdminClient.UploadLicenseFile([]byte{})
|
ok, resp := c.UploadLicenseFile([]byte{})
|
||||||
CheckBadRequestStatus(t, resp)
|
CheckBadRequestStatus(t, resp)
|
||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
})
|
}, "as system admin user")
|
||||||
|
|
||||||
t.Run("as restricted system admin user", func(t *testing.T) {
|
t.Run("as restricted system admin user", func(t *testing.T) {
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
||||||
@@ -67,12 +68,20 @@ func TestUploadLicenseFile(t *testing.T) {
|
|||||||
CheckForbiddenStatus(t, resp)
|
CheckForbiddenStatus(t, resp)
|
||||||
require.False(t, ok)
|
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) {
|
func TestRemoveLicenseFile(t *testing.T) {
|
||||||
th := Setup(t).InitBasic()
|
th := Setup(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
Client := th.Client
|
Client := th.Client
|
||||||
|
LocalClient := th.LocalClient
|
||||||
|
|
||||||
t.Run("as system user", func(t *testing.T) {
|
t.Run("as system user", func(t *testing.T) {
|
||||||
ok, resp := Client.RemoveLicenseFile()
|
ok, resp := Client.RemoveLicenseFile()
|
||||||
@@ -80,11 +89,11 @@ func TestRemoveLicenseFile(t *testing.T) {
|
|||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("as system admin user", func(t *testing.T) {
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
|
||||||
ok, resp := th.SystemAdminClient.RemoveLicenseFile()
|
ok, resp := c.RemoveLicenseFile()
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
})
|
}, "as system admin user")
|
||||||
|
|
||||||
t.Run("as restricted system admin user", func(t *testing.T) {
|
t.Run("as restricted system admin user", func(t *testing.T) {
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
||||||
@@ -93,4 +102,12 @@ func TestRemoveLicenseFile(t *testing.T) {
|
|||||||
CheckForbiddenStatus(t, resp)
|
CheckForbiddenStatus(t, resp)
|
||||||
require.False(t, ok)
|
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
Обычный файл
13
api4/plugin_local.go
Обычный файл
@@ -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()
|
th := Setup(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
statesJson, err := json.Marshal(th.App.Config().PluginSettings.PluginStates)
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||||
require.Nil(t, err)
|
statesJson, err := json.Marshal(th.App.Config().PluginSettings.PluginStates)
|
||||||
states := map[string]*model.PluginState{}
|
require.Nil(t, err)
|
||||||
json.Unmarshal(statesJson, &states)
|
states := map[string]*model.PluginState{}
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
json.Unmarshal(statesJson, &states)
|
||||||
*cfg.PluginSettings.Enable = true
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||||
*cfg.PluginSettings.EnableUploads = true
|
*cfg.PluginSettings.Enable = true
|
||||||
*cfg.PluginSettings.AllowInsecureDownloadUrl = true
|
*cfg.PluginSettings.EnableUploads = true
|
||||||
})
|
*cfg.PluginSettings.AllowInsecureDownloadUrl = true
|
||||||
|
})
|
||||||
|
|
||||||
path, _ := fileutils.FindDir("tests")
|
path, _ := fileutils.FindDir("tests")
|
||||||
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
|
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Install from URL
|
// Install from URL
|
||||||
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
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
|
|
||||||
res.WriteHeader(http.StatusOK)
|
res.WriteHeader(http.StatusOK)
|
||||||
res.Write(tarData)
|
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)
|
CheckNoError(t, resp)
|
||||||
assert.Equal(t, "testplugin", manifest.Id)
|
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) {
|
func TestNotifyClusterPluginEvent(t *testing.T) {
|
||||||
@@ -387,80 +389,82 @@ func TestDisableOnRemove(t *testing.T) {
|
|||||||
th := Setup(t).InitBasic()
|
th := Setup(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||||
*cfg.PluginSettings.Enable = true
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||||
*cfg.PluginSettings.EnableUploads = true
|
*cfg.PluginSettings.Enable = true
|
||||||
})
|
*cfg.PluginSettings.EnableUploads = true
|
||||||
|
})
|
||||||
|
|
||||||
// Upload
|
// Upload
|
||||||
manifest, resp := th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
|
manifest, resp := client.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))
|
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
require.Equal(t, "testplugin", manifest.Id)
|
require.Equal(t, "testplugin", manifest.Id)
|
||||||
|
|
||||||
// Plugin should remain active
|
// Check initial status
|
||||||
pluginsResp, resp = th.SystemAdminClient.GetPlugins()
|
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)
|
CheckNoError(t, resp)
|
||||||
require.Empty(t, pluginsResp.Inactive)
|
require.Empty(t, pluginsResp.Inactive)
|
||||||
require.Equal(t, pluginsResp.Active, []*model.PluginInfo{{
|
require.Equal(t, pluginsResp.Active, []*model.PluginInfo{{
|
||||||
Manifest: *manifest,
|
Manifest: *manifest,
|
||||||
}})
|
}})
|
||||||
}
|
|
||||||
|
|
||||||
// Remove plugin
|
if tc.Upgrade {
|
||||||
ok, resp = th.SystemAdminClient.RemovePlugin(manifest.Id)
|
// Upgrade
|
||||||
CheckNoError(t, resp)
|
manifest, resp = client.UploadPluginForced(bytes.NewReader(tarData))
|
||||||
require.True(t, ok)
|
CheckNoError(t, resp)
|
||||||
|
require.Equal(t, "testplugin", manifest.Id)
|
||||||
|
|
||||||
// Plugin should have no status
|
// Plugin should remain active
|
||||||
pluginsResp, resp = th.SystemAdminClient.GetPlugins()
|
pluginsResp, resp = client.GetPlugins()
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
require.Empty(t, pluginsResp.Inactive)
|
require.Empty(t, pluginsResp.Inactive)
|
||||||
require.Empty(t, pluginsResp.Active)
|
require.Equal(t, pluginsResp.Active, []*model.PluginInfo{{
|
||||||
|
Manifest: *manifest,
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
|
||||||
// Upload same plugin
|
// Remove plugin
|
||||||
manifest, resp = th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
|
ok, resp = client.RemovePlugin(manifest.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
require.Equal(t, "testplugin", manifest.Id)
|
require.True(t, ok)
|
||||||
|
|
||||||
// Plugin should be inactive
|
// Plugin should have no status
|
||||||
pluginsResp, resp = th.SystemAdminClient.GetPlugins()
|
pluginsResp, resp = client.GetPlugins()
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
require.Empty(t, pluginsResp.Active)
|
require.Empty(t, pluginsResp.Inactive)
|
||||||
require.Equal(t, pluginsResp.Inactive, []*model.PluginInfo{{
|
require.Empty(t, pluginsResp.Active)
|
||||||
Manifest: *manifest,
|
|
||||||
}})
|
|
||||||
|
|
||||||
// Clean up
|
// Upload same plugin
|
||||||
ok, resp = th.SystemAdminClient.RemovePlugin(manifest.Id)
|
manifest, resp = client.UploadPlugin(bytes.NewReader(tarData))
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
require.True(t, ok)
|
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
Обычный файл
8
api4/team_local.go
Обычный файл
@@ -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")
|
||||||
|
}
|
||||||
@@ -16,6 +16,9 @@ func (a *App) MakePermissionError(permission *model.Permission) *model.AppError
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) SessionHasPermissionTo(session model.Session, permission *model.Permission) bool {
|
func (a *App) SessionHasPermissionTo(session model.Session, permission *model.Permission) bool {
|
||||||
|
if session.IsUnrestricted() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
return a.RolesGrantPermission(session.GetUserRoles(), permission.Id)
|
return a.RolesGrantPermission(session.GetUserRoles(), permission.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ type Server struct {
|
|||||||
// RootRouter is the starting point for all HTTP requests to the server.
|
// RootRouter is the starting point for all HTTP requests to the server.
|
||||||
RootRouter *mux.Router
|
RootRouter *mux.Router
|
||||||
|
|
||||||
|
// LocalRouter is the starting point for all the local UNIX socket
|
||||||
|
// requests to the server
|
||||||
|
LocalRouter *mux.Router
|
||||||
|
|
||||||
// Router is the starting point for all web, api4 and ws requests to the server. It differs
|
// Router is the starting point for all web, api4 and ws requests to the server. It differs
|
||||||
// from RootRouter only if the SiteURL contains a /subpath.
|
// from RootRouter only if the SiteURL contains a /subpath.
|
||||||
Router *mux.Router
|
Router *mux.Router
|
||||||
@@ -64,6 +68,8 @@ type Server struct {
|
|||||||
RateLimiter *RateLimiter
|
RateLimiter *RateLimiter
|
||||||
Busy *Busy
|
Busy *Busy
|
||||||
|
|
||||||
|
localModeServer *http.Server
|
||||||
|
|
||||||
didFinishListen chan struct{}
|
didFinishListen chan struct{}
|
||||||
|
|
||||||
goroutineCount int32
|
goroutineCount int32
|
||||||
@@ -154,10 +160,12 @@ type Server struct {
|
|||||||
|
|
||||||
func NewServer(options ...Option) (*Server, error) {
|
func NewServer(options ...Option) (*Server, error) {
|
||||||
rootRouter := mux.NewRouter()
|
rootRouter := mux.NewRouter()
|
||||||
|
localRouter := mux.NewRouter()
|
||||||
|
|
||||||
s := &Server{
|
s := &Server{
|
||||||
goroutineExitSignal: make(chan struct{}, 1),
|
goroutineExitSignal: make(chan struct{}, 1),
|
||||||
RootRouter: rootRouter,
|
RootRouter: rootRouter,
|
||||||
|
LocalRouter: localRouter,
|
||||||
licenseListeners: map[string]func(*model.License, *model.License){},
|
licenseListeners: map[string]func(*model.License, *model.License){},
|
||||||
clientConfig: make(map[string]string),
|
clientConfig: make(map[string]string),
|
||||||
}
|
}
|
||||||
@@ -433,6 +441,8 @@ func (s *Server) Shutdown() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.StopHTTPServer()
|
s.StopHTTPServer()
|
||||||
|
s.stopLocalModeServer()
|
||||||
|
|
||||||
s.WaitForGoroutines()
|
s.WaitForGoroutines()
|
||||||
|
|
||||||
if s.htmlTemplateWatcher != nil {
|
if s.htmlTemplateWatcher != nil {
|
||||||
@@ -712,9 +722,44 @@ func (s *Server) Start() error {
|
|||||||
close(s.didFinishListen)
|
close(s.didFinishListen)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
if *s.Config().ServiceSettings.EnableLocalMode {
|
||||||
|
if err := s.startLocalModeServer(); err != nil {
|
||||||
|
mlog.Critical(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) startLocalModeServer() error {
|
||||||
|
s.localModeServer = &http.Server{
|
||||||
|
Handler: s.LocalRouter,
|
||||||
|
}
|
||||||
|
|
||||||
|
socket := *s.configStore.Get().ServiceSettings.LocalModeSocketLocation
|
||||||
|
unixListener, err := net.Listen("unix", socket)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, utils.T("api.server.start_server.starting.critical"), err)
|
||||||
|
}
|
||||||
|
if err = os.Chmod(socket, 0600); err != nil {
|
||||||
|
return errors.Wrapf(err, utils.T("api.server.start_server.starting.critical"), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err = s.localModeServer.Serve(unixListener)
|
||||||
|
if err != nil && err != http.ErrServerClosed {
|
||||||
|
mlog.Critical("Error starting unix socket server", mlog.Err(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) stopLocalModeServer() {
|
||||||
|
if s.localModeServer != nil {
|
||||||
|
s.localModeServer.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) OriginChecker() func(*http.Request) bool {
|
func (a *App) OriginChecker() func(*http.Request) bool {
|
||||||
if allowed := *a.Config().ServiceSettings.AllowCorsFrom; allowed != "" {
|
if allowed := *a.Config().ServiceSettings.AllowCorsFrom; allowed != "" {
|
||||||
if allowed != "*" {
|
if allowed != "*" {
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ func runServer(configStore config.Store, disableConfigWatch bool, usedPlatform b
|
|||||||
api := api4.Init(server, server.AppOptions, server.Router)
|
api := api4.Init(server, server.AppOptions, server.Router)
|
||||||
wsapi.Init(server.FakeApp(), server.WebSocketRouter)
|
wsapi.Init(server.FakeApp(), server.WebSocketRouter)
|
||||||
web.New(server, server.AppOptions, server.Router)
|
web.New(server, server.AppOptions, server.Router)
|
||||||
|
api4.InitLocal(server, server.AppOptions, server.LocalRouter)
|
||||||
|
|
||||||
serverErr := server.Start()
|
serverErr := server.Start()
|
||||||
if serverErr != nil {
|
if serverErr != nil {
|
||||||
|
|||||||
@@ -1138,6 +1138,10 @@
|
|||||||
"id": "api.context.invalid_url_param.app_error",
|
"id": "api.context.invalid_url_param.app_error",
|
||||||
"translation": "Invalid or missing {{.Name}} parameter in request URL."
|
"translation": "Invalid or missing {{.Name}} parameter in request URL."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.context.local_origin_required.app_error",
|
||||||
|
"translation": "This endpoint requires a local request origin."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.context.mfa_required.app_error",
|
"id": "api.context.mfa_required.app_error",
|
||||||
"translation": "Multi-factor authentication is required on this server."
|
"translation": "Multi-factor authentication is required on this server."
|
||||||
|
|||||||
@@ -213,6 +213,8 @@ const (
|
|||||||
OFFICE365_SETTINGS_DEFAULT_AUTH_ENDPOINT = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
|
OFFICE365_SETTINGS_DEFAULT_AUTH_ENDPOINT = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
|
||||||
OFFICE365_SETTINGS_DEFAULT_TOKEN_ENDPOINT = "https://login.microsoftonline.com/common/oauth2/v2.0/token"
|
OFFICE365_SETTINGS_DEFAULT_TOKEN_ENDPOINT = "https://login.microsoftonline.com/common/oauth2/v2.0/token"
|
||||||
OFFICE365_SETTINGS_DEFAULT_USER_API_ENDPOINT = "https://graph.microsoft.com/v1.0/me"
|
OFFICE365_SETTINGS_DEFAULT_USER_API_ENDPOINT = "https://graph.microsoft.com/v1.0/me"
|
||||||
|
|
||||||
|
LOCAL_MODE_SOCKET_PATH = "/var/tmp/mattermost_local.socket"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ServerTLSSupportedCiphers = map[string]uint16{
|
var ServerTLSSupportedCiphers = map[string]uint16{
|
||||||
@@ -329,6 +331,8 @@ type ServiceSettings struct {
|
|||||||
EnableBotAccountCreation *bool
|
EnableBotAccountCreation *bool
|
||||||
EnableSVGs *bool
|
EnableSVGs *bool
|
||||||
EnableLatex *bool
|
EnableLatex *bool
|
||||||
|
EnableLocalMode *bool
|
||||||
|
LocalModeSocketLocation *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServiceSettings) SetDefaults(isUpdate bool) {
|
func (s *ServiceSettings) SetDefaults(isUpdate bool) {
|
||||||
@@ -722,6 +726,14 @@ func (s *ServiceSettings) SetDefaults(isUpdate bool) {
|
|||||||
s.EnableLatex = NewBool(false)
|
s.EnableLatex = NewBool(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.EnableLocalMode == nil {
|
||||||
|
s.EnableLocalMode = NewBool(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.LocalModeSocketLocation == nil {
|
||||||
|
s.LocalModeSocketLocation = NewString(LOCAL_MODE_SOCKET_PATH)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClusterSettings struct {
|
type ClusterSettings struct {
|
||||||
|
|||||||
@@ -39,6 +39,13 @@ type Session struct {
|
|||||||
IsOAuth bool `json:"is_oauth"`
|
IsOAuth bool `json:"is_oauth"`
|
||||||
Props StringMap `json:"props"`
|
Props StringMap `json:"props"`
|
||||||
TeamMembers []*TeamMember `json:"team_members" db:"-"`
|
TeamMembers []*TeamMember `json:"team_members" db:"-"`
|
||||||
|
Local bool `json:"local" db:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the session is unrestricted, which should grant it
|
||||||
|
// with all permissions. This is used for local mode sessions
|
||||||
|
func (me *Session) IsUnrestricted() bool {
|
||||||
|
return me.Local
|
||||||
}
|
}
|
||||||
|
|
||||||
func (me *Session) DeepCopy() *Session {
|
func (me *Session) DeepCopy() *Session {
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ func (w *Web) NewHandler(h func(*Context, http.ResponseWriter, *http.Request)) h
|
|||||||
TrustRequester: false,
|
TrustRequester: false,
|
||||||
RequireMfa: false,
|
RequireMfa: false,
|
||||||
IsStatic: false,
|
IsStatic: false,
|
||||||
|
IsLocal: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +75,7 @@ type Handler struct {
|
|||||||
TrustRequester bool
|
TrustRequester bool
|
||||||
RequireMfa bool
|
RequireMfa bool
|
||||||
IsStatic bool
|
IsStatic bool
|
||||||
|
IsLocal bool
|
||||||
DisableWhenBusy bool
|
DisableWhenBusy bool
|
||||||
|
|
||||||
cspShaDirective string
|
cspShaDirective string
|
||||||
@@ -210,6 +212,17 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
c.SetServerBusyError()
|
c.SetServerBusyError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Err == nil && h.IsLocal {
|
||||||
|
// if the connection is local, RemoteAddr shouldn't have the
|
||||||
|
// shape IP:PORT (it will be "@" in Linux, for example)
|
||||||
|
isLocalOrigin := !strings.Contains(r.RemoteAddr, ":")
|
||||||
|
if *c.App.Config().ServiceSettings.EnableLocalMode && isLocalOrigin {
|
||||||
|
c.App.SetSession(&model.Session{Local: true})
|
||||||
|
} else if !isLocalOrigin {
|
||||||
|
c.Err = model.NewAppError("", "api.context.local_origin_required.app_error", nil, "LocalOriginRequired", http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if c.Err == nil {
|
if c.Err == nil {
|
||||||
h.HandleFunc(c, w, r)
|
h.HandleFunc(c, w, r)
|
||||||
}
|
}
|
||||||
@@ -323,6 +336,7 @@ func (w *Web) ApiHandler(h func(*Context, http.ResponseWriter, *http.Request)) h
|
|||||||
TrustRequester: false,
|
TrustRequester: false,
|
||||||
RequireMfa: false,
|
RequireMfa: false,
|
||||||
IsStatic: false,
|
IsStatic: false,
|
||||||
|
IsLocal: false,
|
||||||
}
|
}
|
||||||
if *w.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
if *w.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||||
return gziphandler.GzipHandler(handler)
|
return gziphandler.GzipHandler(handler)
|
||||||
@@ -342,6 +356,7 @@ func (w *Web) ApiHandlerTrustRequester(h func(*Context, http.ResponseWriter, *ht
|
|||||||
TrustRequester: true,
|
TrustRequester: true,
|
||||||
RequireMfa: false,
|
RequireMfa: false,
|
||||||
IsStatic: false,
|
IsStatic: false,
|
||||||
|
IsLocal: false,
|
||||||
}
|
}
|
||||||
if *w.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
if *w.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||||
return gziphandler.GzipHandler(handler)
|
return gziphandler.GzipHandler(handler)
|
||||||
@@ -360,6 +375,7 @@ func (w *Web) ApiSessionRequired(h func(*Context, http.ResponseWriter, *http.Req
|
|||||||
TrustRequester: false,
|
TrustRequester: false,
|
||||||
RequireMfa: true,
|
RequireMfa: true,
|
||||||
IsStatic: false,
|
IsStatic: false,
|
||||||
|
IsLocal: false,
|
||||||
}
|
}
|
||||||
if *w.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
if *w.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||||
return gziphandler.GzipHandler(handler)
|
return gziphandler.GzipHandler(handler)
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user