mirror of
https://github.com/mattermost/mattermost.git
synced 2026-02-03 20:40:00 -05:00
* MM-17149 - Extend config.json for marketplace settings (#11933)
* MM-17149 - Extend config.json for marketplace settings
* Renamed MarketplaceUrl, tracking default marketplace url
* Added EnableMarketplace to the client config
* Revert "Added EnableMarketplace to the client config"
This reverts commit 0f982c4c66.
* MM-17149 - Added EnableMarketplace to the client config (#11958)
* Added EnableMarketplace to the client config
* Moved EnableMarketplace setting out of limited client configuration
* Add public key settings to the config.json
* Rename PublicKeys to SignaturePublicKeyFiles
* Change filepath.Split to Base
* Remove additional prints
* Force extention of a public key file
* Remove config validation
* Remove error on delete
* Remove config cloning
* Add error messages
* Add plugin public key tests
* Rename extension to PluginSignaturePublicKeyFileExtention
* Remove EnforceVerification
* Change []*PublicKeyDescription to []string
* Change .asc extension to .plugin.asc
* Change ordering of public methods
* Change plugin key commands
* Update examples in the plugin key commands
* Remove forcing extention
* Add verify signature in settings
* Fix tabbing
* Fix naming
* Remove unused text
* Remove unused text
* Update command examples
* Fix unit tests
* Change errors.New to errors.Wrap
* Fix verbose flag
* Change .asc to .gpg
* Fix }
* Change AddPublicKey signature
* Change public.key extension
* Add plugin public key command tests
* Update en.json
* Bootstrap the public keys
* Update en.json
* Fix en.json
* Fix en.json
* Bootstrap hard-coded public key
* Remove unused texts in en.json
* Change file to name
* Add license header
* Implement plugin signature verification
* Remove benburker openpgp
* Update en.json
* Update development public key
* Add support of multiple signatures in filestore
* Update en.json
* Run go mod vendor
* Fix style
* Remove writeFile method
* Remove .plugin.asc extension
* Rename publiKey to mattermostPublicKey
* Verify plugin with mattermost public key
* Remove init_public_keys string
* Add InstallPluginWithSignature method and Refactor
* Add signature verification on claster notification
* Remove armored signature headers
* Add error strings
* Fix en.json
* Change signatureStorePath
* Implement minor fixes
* Refactor plugin install methods
* Add installPlugin method to uploadPlugin
* Update en.json
* Refactor installPlugin
* Limit number of signatures
* Close signatures
* Fix helper function
* Fix fromReadCloseSeekerToReadSeeker
* Cleaned up ReadCloseSeeker for signatures
* Remove signature truncation on FS
* GolangCI
* Add tests for armored signatures and plugin uploads
* Fix nil slice issue
* Fix TestPluginSync
* Fixed tests
* Return io.ReadSeeker from downloadFromUrl
* Add log for the found plugins in the file store
* Remove logging plugin detection info
959 lines
30 KiB
Go
959 lines
30 KiB
Go
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
|
// See License.txt for license information.
|
|
|
|
package api4
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/mattermost/mattermost-server/model"
|
|
"github.com/mattermost/mattermost-server/testlib"
|
|
"github.com/mattermost/mattermost-server/utils/fileutils"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestPlugin(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
statesJson, _ := json.Marshal(th.App.Config().PluginSettings.PluginStates)
|
|
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)
|
|
|
|
// 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)
|
|
|
|
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 = th.SystemAdminClient.InstallPluginFromUrl(slowTestServer.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)
|
|
|
|
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)
|
|
|
|
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")
|
|
CheckBadRequestStatus(t, resp)
|
|
assert.False(t, ok)
|
|
|
|
ok, resp = th.SystemAdminClient.EnablePlugin("JUNK")
|
|
CheckBadRequestStatus(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")
|
|
CheckBadRequestStatus(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)
|
|
CheckBadRequestStatus(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")
|
|
CheckBadRequestStatus(t, resp)
|
|
}
|
|
|
|
func TestNotifyClusterPluginEvent(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
testCluster := &testlib.FakeClusterInterface{}
|
|
th.Server.Cluster = testCluster
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.Enable = true
|
|
*cfg.PluginSettings.EnableUploads = true
|
|
})
|
|
|
|
path, _ := fileutils.FindDir("tests")
|
|
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
|
|
require.NoError(t, err)
|
|
|
|
testCluster.ClearMessages()
|
|
|
|
// Successful upload
|
|
manifest, resp := th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
|
|
CheckNoError(t, resp)
|
|
require.Equal(t, "testplugin", manifest.Id)
|
|
|
|
// Stored in File Store: Upload Plugin case
|
|
expectedPath := filepath.Join("./plugins", manifest.Id) + ".tar.gz"
|
|
pluginStored, err := th.App.FileExists(expectedPath)
|
|
require.Nil(t, err)
|
|
require.True(t, pluginStored)
|
|
|
|
messages := testCluster.GetMessages()
|
|
expectedPluginData := model.PluginEventData{
|
|
Id: manifest.Id,
|
|
}
|
|
expectedInstallMessage := &model.ClusterMessage{
|
|
Event: model.CLUSTER_EVENT_INSTALL_PLUGIN,
|
|
SendType: model.CLUSTER_SEND_RELIABLE,
|
|
WaitForAllToSend: true,
|
|
Data: expectedPluginData.ToJson(),
|
|
}
|
|
actualMessages := findClusterMessages(model.CLUSTER_EVENT_INSTALL_PLUGIN, messages)
|
|
require.Equal(t, []*model.ClusterMessage{expectedInstallMessage}, actualMessages)
|
|
|
|
// Upgrade
|
|
testCluster.ClearMessages()
|
|
manifest, resp = th.SystemAdminClient.UploadPluginForced(bytes.NewReader(tarData))
|
|
CheckNoError(t, resp)
|
|
require.Equal(t, "testplugin", manifest.Id)
|
|
|
|
// Successful remove
|
|
webSocketClient, err := th.CreateWebSocketSystemAdminClient()
|
|
require.Nil(t, err)
|
|
webSocketClient.Listen()
|
|
defer webSocketClient.Close()
|
|
done := make(chan bool)
|
|
go func() {
|
|
for {
|
|
select {
|
|
case resp := <-webSocketClient.EventChannel:
|
|
if resp.Event == model.WEBSOCKET_EVENT_PLUGIN_STATUSES_CHANGED && len(resp.Data["plugin_statuses"].([]interface{})) == 0 {
|
|
done <- true
|
|
return
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
done <- false
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
testCluster.ClearMessages()
|
|
ok, resp := th.SystemAdminClient.RemovePlugin(manifest.Id)
|
|
CheckNoError(t, resp)
|
|
require.True(t, ok)
|
|
|
|
result := <-done
|
|
require.True(t, result, "plugin_statuses_changed websocket event was not received")
|
|
|
|
messages = testCluster.GetMessages()
|
|
|
|
expectedRemoveMessage := &model.ClusterMessage{
|
|
Event: model.CLUSTER_EVENT_REMOVE_PLUGIN,
|
|
SendType: model.CLUSTER_SEND_RELIABLE,
|
|
WaitForAllToSend: true,
|
|
Data: expectedPluginData.ToJson(),
|
|
}
|
|
actualMessages = findClusterMessages(model.CLUSTER_EVENT_REMOVE_PLUGIN, messages)
|
|
require.Equal(t, []*model.ClusterMessage{expectedRemoveMessage}, actualMessages)
|
|
|
|
pluginStored, err = th.App.FileExists(expectedPath)
|
|
require.Nil(t, err)
|
|
require.False(t, pluginStored)
|
|
}
|
|
|
|
func TestDisableOnRemove(t *testing.T) {
|
|
path, _ := fileutils.FindDir("tests")
|
|
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
|
|
require.NoError(t, err)
|
|
|
|
testCases := []struct {
|
|
Description string
|
|
Upgrade bool
|
|
}{
|
|
{
|
|
"Remove without upgrading",
|
|
false,
|
|
},
|
|
{
|
|
"Remove after upgrading",
|
|
true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Description, func(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
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.Len(t, pluginsResp.Active, 0)
|
|
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.Len(t, pluginsResp.Inactive, 0)
|
|
require.Equal(t, pluginsResp.Active, []*model.PluginInfo{{
|
|
Manifest: *manifest,
|
|
}})
|
|
|
|
if tc.Upgrade {
|
|
// Upgrade
|
|
manifest, resp = th.SystemAdminClient.UploadPluginForced(bytes.NewReader(tarData))
|
|
CheckNoError(t, resp)
|
|
require.Equal(t, "testplugin", manifest.Id)
|
|
|
|
// Plugin should remain active
|
|
pluginsResp, resp = th.SystemAdminClient.GetPlugins()
|
|
CheckNoError(t, resp)
|
|
require.Len(t, pluginsResp.Inactive, 0)
|
|
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)
|
|
|
|
// Plugin should have no status
|
|
pluginsResp, resp = th.SystemAdminClient.GetPlugins()
|
|
CheckNoError(t, resp)
|
|
require.Len(t, pluginsResp.Inactive, 0)
|
|
require.Len(t, pluginsResp.Active, 0)
|
|
|
|
// Upload same plugin
|
|
manifest, resp = th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
|
|
CheckNoError(t, resp)
|
|
require.Equal(t, "testplugin", manifest.Id)
|
|
|
|
// Plugin should be inactive
|
|
pluginsResp, resp = th.SystemAdminClient.GetPlugins()
|
|
CheckNoError(t, resp)
|
|
require.Len(t, pluginsResp.Active, 0)
|
|
require.Equal(t, pluginsResp.Inactive, []*model.PluginInfo{{
|
|
Manifest: *manifest,
|
|
}})
|
|
|
|
// Clean up
|
|
ok, resp = th.SystemAdminClient.RemovePlugin(manifest.Id)
|
|
CheckNoError(t, resp)
|
|
require.True(t, ok)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetMarketplacePlugins(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.Enable = true
|
|
*cfg.PluginSettings.EnableUploads = true
|
|
*cfg.PluginSettings.EnableMarketplace = false
|
|
})
|
|
|
|
t.Run("marketplace disabled", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.EnableMarketplace = false
|
|
*cfg.PluginSettings.MarketplaceUrl = "invalid.com"
|
|
})
|
|
|
|
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
|
CheckNotImplementedStatus(t, resp)
|
|
require.Nil(t, plugins)
|
|
})
|
|
|
|
t.Run("no server", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.EnableMarketplace = true
|
|
*cfg.PluginSettings.MarketplaceUrl = "invalid.com"
|
|
})
|
|
|
|
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
|
CheckInternalErrorStatus(t, resp)
|
|
require.Nil(t, plugins)
|
|
})
|
|
|
|
t.Run("no permission", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.EnableMarketplace = true
|
|
*cfg.PluginSettings.MarketplaceUrl = "invalid.com"
|
|
})
|
|
|
|
plugins, resp := th.Client.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
|
CheckForbiddenStatus(t, resp)
|
|
require.Nil(t, plugins)
|
|
})
|
|
|
|
t.Run("empty response from server", func(t *testing.T) {
|
|
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
res.WriteHeader(http.StatusOK)
|
|
json, err := json.Marshal([]*model.MarketplacePlugin{})
|
|
require.NoError(t, err)
|
|
res.Write(json)
|
|
}))
|
|
defer func() { testServer.Close() }()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.EnableMarketplace = true
|
|
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
|
|
})
|
|
|
|
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
|
CheckNoError(t, resp)
|
|
require.Len(t, plugins, 0)
|
|
})
|
|
|
|
t.Run("verify server version is passed through", func(t *testing.T) {
|
|
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
serverVersion, ok := req.URL.Query()["server_version"]
|
|
require.True(t, ok)
|
|
require.Len(t, serverVersion, 1)
|
|
require.Equal(t, model.CurrentVersion, serverVersion[0])
|
|
require.NotEqual(t, 0, len(serverVersion[0]))
|
|
|
|
res.WriteHeader(http.StatusOK)
|
|
json, err := json.Marshal([]*model.MarketplacePlugin{})
|
|
require.NoError(t, err)
|
|
res.Write(json)
|
|
}))
|
|
defer func() { testServer.Close() }()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.EnableMarketplace = true
|
|
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
|
|
})
|
|
|
|
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
|
CheckNoError(t, resp)
|
|
require.Len(t, plugins, 0)
|
|
})
|
|
}
|
|
|
|
func TestGetInstalledMarketplacePlugins(t *testing.T) {
|
|
samplePlugins := []*model.MarketplacePlugin{
|
|
{
|
|
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
|
|
HomepageURL: "https://example.com/mattermost/mattermost-plugin-nps",
|
|
IconData: "https://example.com/icon.svg",
|
|
DownloadURL: "https://example.com/mattermost/mattermost-plugin-nps/releases/download/v1.0.3/com.mattermost.nps-1.0.3.tar.gz",
|
|
Manifest: &model.Manifest{
|
|
Id: "com.mattermost.nps",
|
|
Name: "User Satisfaction Surveys",
|
|
Description: "This plugin sends quarterly user satisfaction surveys to gather feedback and help improve Mattermost.",
|
|
Version: "1.0.3",
|
|
MinServerVersion: "5.14.0",
|
|
},
|
|
},
|
|
InstalledVersion: "",
|
|
},
|
|
}
|
|
|
|
path, _ := fileutils.FindDir("tests")
|
|
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
|
|
require.NoError(t, err)
|
|
|
|
t.Run("marketplace client returns not-installed plugin", func(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
res.WriteHeader(http.StatusOK)
|
|
json, err := json.Marshal(samplePlugins)
|
|
require.NoError(t, err)
|
|
res.Write(json)
|
|
}))
|
|
defer func() { testServer.Close() }()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.Enable = true
|
|
*cfg.PluginSettings.EnableUploads = true
|
|
*cfg.PluginSettings.EnableMarketplace = true
|
|
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
|
|
})
|
|
|
|
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
|
CheckNoError(t, resp)
|
|
require.Equal(t, samplePlugins, plugins)
|
|
|
|
manifest, resp := th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
|
|
CheckNoError(t, resp)
|
|
|
|
expectedPlugins := append(samplePlugins, &model.MarketplacePlugin{
|
|
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
|
|
HomepageURL: "",
|
|
IconData: "",
|
|
DownloadURL: "",
|
|
Manifest: manifest,
|
|
},
|
|
InstalledVersion: manifest.Version,
|
|
})
|
|
sort.SliceStable(expectedPlugins, func(i, j int) bool {
|
|
return strings.ToLower(expectedPlugins[i].Manifest.Name) < strings.ToLower(expectedPlugins[j].Manifest.Name)
|
|
})
|
|
|
|
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
|
CheckNoError(t, resp)
|
|
require.Equal(t, expectedPlugins, plugins)
|
|
|
|
ok, resp := th.SystemAdminClient.RemovePlugin(manifest.Id)
|
|
CheckNoError(t, resp)
|
|
assert.True(t, ok)
|
|
|
|
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
|
CheckNoError(t, resp)
|
|
require.Equal(t, samplePlugins, plugins)
|
|
})
|
|
|
|
t.Run("marketplace client returns installed plugin", func(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.Enable = true
|
|
*cfg.PluginSettings.EnableUploads = true
|
|
*cfg.PluginSettings.EnableMarketplace = true
|
|
})
|
|
|
|
manifest, resp := th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
|
|
CheckNoError(t, resp)
|
|
|
|
newPlugin := &model.MarketplacePlugin{
|
|
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
|
|
HomepageURL: "HomepageURL",
|
|
IconData: "IconData",
|
|
DownloadURL: "DownloadURL",
|
|
Manifest: manifest,
|
|
},
|
|
InstalledVersion: manifest.Version,
|
|
}
|
|
expectedPlugins := append(samplePlugins, newPlugin)
|
|
sort.SliceStable(expectedPlugins, func(i, j int) bool {
|
|
return strings.ToLower(expectedPlugins[i].Manifest.Name) < strings.ToLower(expectedPlugins[j].Manifest.Name)
|
|
})
|
|
|
|
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
res.WriteHeader(http.StatusOK)
|
|
json, err := json.Marshal([]*model.MarketplacePlugin{samplePlugins[0], newPlugin})
|
|
require.NoError(t, err)
|
|
res.Write(json)
|
|
}))
|
|
defer func() { testServer.Close() }()
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
|
|
})
|
|
|
|
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
|
CheckNoError(t, resp)
|
|
require.Equal(t, expectedPlugins, plugins)
|
|
|
|
ok, resp := th.SystemAdminClient.RemovePlugin(manifest.Id)
|
|
CheckNoError(t, resp)
|
|
assert.True(t, ok)
|
|
|
|
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
|
CheckNoError(t, resp)
|
|
newPlugin.InstalledVersion = manifest.Version
|
|
require.Equal(t, expectedPlugins, plugins)
|
|
})
|
|
}
|
|
|
|
func TestSearchGetMarketplacePlugins(t *testing.T) {
|
|
samplePlugins := []*model.MarketplacePlugin{
|
|
{
|
|
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
|
|
HomepageURL: "example.com/mattermost/mattermost-plugin-nps",
|
|
IconData: "Cjxzdmcgdmlld0JveD0nMCAwIDEwNSA5MycgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJz4KPHBhdGggZD0nTTY2LDBoMzl2OTN6TTM4LDBoLTM4djkzek01MiwzNWwyNSw1OGgtMTZsLTgtMThoLTE4eicgZmlsbD0nI0VEMUMyNCcvPgo8L3N2Zz4K",
|
|
DownloadURL: "example.com/mattermost/mattermost-plugin-nps/releases/download/v1.0.3/com.mattermost.nps-1.0.3.tar.gz",
|
|
Manifest: &model.Manifest{
|
|
Id: "com.mattermost.nps",
|
|
Name: "User Satisfaction Surveys",
|
|
Description: "This plugin sends quarterly user satisfaction surveys to gather feedback and help improve Mattermost.",
|
|
Version: "1.0.3",
|
|
MinServerVersion: "5.14.0",
|
|
},
|
|
},
|
|
InstalledVersion: "",
|
|
},
|
|
}
|
|
|
|
path, _ := fileutils.FindDir("tests")
|
|
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
|
|
require.NoError(t, err)
|
|
|
|
tarDataV2, err := ioutil.ReadFile(filepath.Join(path, "testpluginv2.tar.gz"))
|
|
require.NoError(t, err)
|
|
|
|
t.Run("search installed plugin", func(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
res.WriteHeader(http.StatusOK)
|
|
json, err := json.Marshal(samplePlugins)
|
|
require.NoError(t, err)
|
|
res.Write(json)
|
|
}))
|
|
defer func() { testServer.Close() }()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.Enable = true
|
|
*cfg.PluginSettings.EnableUploads = true
|
|
*cfg.PluginSettings.EnableMarketplace = true
|
|
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
|
|
})
|
|
|
|
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
|
CheckNoError(t, resp)
|
|
require.Equal(t, samplePlugins, plugins)
|
|
|
|
manifest, resp := th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
|
|
CheckNoError(t, resp)
|
|
|
|
newPluginV1 := &model.MarketplacePlugin{
|
|
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
|
|
HomepageURL: "",
|
|
IconData: "",
|
|
DownloadURL: "",
|
|
Manifest: manifest,
|
|
},
|
|
InstalledVersion: manifest.Version,
|
|
}
|
|
expectedPlugins := append(samplePlugins, newPluginV1)
|
|
|
|
manifest, resp = th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarDataV2))
|
|
CheckNoError(t, resp)
|
|
newPluginV2 := &model.MarketplacePlugin{
|
|
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
|
|
HomepageURL: "",
|
|
IconData: "",
|
|
DownloadURL: "",
|
|
Manifest: manifest,
|
|
},
|
|
InstalledVersion: manifest.Version,
|
|
}
|
|
expectedPlugins = append(expectedPlugins, newPluginV2)
|
|
sort.SliceStable(expectedPlugins, func(i, j int) bool {
|
|
return strings.ToLower(expectedPlugins[i].Manifest.Name) < strings.ToLower(expectedPlugins[j].Manifest.Name)
|
|
})
|
|
|
|
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
|
CheckNoError(t, resp)
|
|
require.Equal(t, expectedPlugins, plugins)
|
|
|
|
// Search for plugins from the server
|
|
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{Filter: "testplugin_v2"})
|
|
CheckNoError(t, resp)
|
|
require.Equal(t, []*model.MarketplacePlugin{newPluginV2}, plugins)
|
|
|
|
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{Filter: "dsgsdg_v2"})
|
|
CheckNoError(t, resp)
|
|
require.Equal(t, []*model.MarketplacePlugin{newPluginV2}, plugins)
|
|
|
|
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{Filter: "User Satisfaction Surveys"})
|
|
CheckNoError(t, resp)
|
|
require.Equal(t, samplePlugins, plugins)
|
|
|
|
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{Filter: "NOFILTER"})
|
|
CheckNoError(t, resp)
|
|
require.Nil(t, plugins)
|
|
})
|
|
}
|
|
|
|
func TestInstallMarketplacePlugin(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.Enable = true
|
|
*cfg.PluginSettings.EnableUploads = true
|
|
*cfg.PluginSettings.EnableMarketplace = false
|
|
})
|
|
path, _ := fileutils.FindDir("tests")
|
|
signatureFilename := "testpluginv2.tar.gz.sig"
|
|
signatureFileReader, err := os.Open(filepath.Join(path, signatureFilename))
|
|
require.Nil(t, err)
|
|
sigFile, err := ioutil.ReadAll(signatureFileReader)
|
|
require.Nil(t, err)
|
|
pluginSignature := base64.StdEncoding.EncodeToString(sigFile)
|
|
|
|
tarData, err := ioutil.ReadFile(filepath.Join(path, "testpluginv2.tar.gz"))
|
|
require.NoError(t, err)
|
|
pluginServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
res.WriteHeader(http.StatusOK)
|
|
res.Write(tarData)
|
|
}))
|
|
defer pluginServer.Close()
|
|
|
|
samplePlugins := []*model.MarketplacePlugin{
|
|
{
|
|
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
|
|
HomepageURL: "https://example.com/mattermost/mattermost-plugin-nps",
|
|
IconData: "https://example.com/icon.svg",
|
|
DownloadURL: pluginServer.URL,
|
|
Manifest: &model.Manifest{
|
|
Id: "testplugin_v2",
|
|
Name: "testplugin_v2",
|
|
Description: "dsgsdg_v2",
|
|
Version: "1.2.2",
|
|
MinServerVersion: "",
|
|
},
|
|
},
|
|
InstalledVersion: "",
|
|
},
|
|
{
|
|
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
|
|
HomepageURL: "https://example.com/mattermost/mattermost-plugin-nps",
|
|
IconData: "https://example.com/icon.svg",
|
|
DownloadURL: pluginServer.URL,
|
|
Manifest: &model.Manifest{
|
|
Id: "testplugin_v2",
|
|
Name: "testplugin_v2",
|
|
Description: "dsgsdg_v2",
|
|
Version: "1.2.3",
|
|
MinServerVersion: "",
|
|
},
|
|
Signatures: []*model.PluginSignature{{Signature: pluginSignature, PublicKeyHash: "F3FACE"}},
|
|
},
|
|
InstalledVersion: "",
|
|
},
|
|
}
|
|
request := &model.InstallMarketplacePluginRequest{Id: "", Version: ""}
|
|
t.Run("marketplace disabled", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.EnableMarketplace = false
|
|
*cfg.PluginSettings.MarketplaceUrl = "invalid.com"
|
|
})
|
|
plugin, resp := th.SystemAdminClient.InstallMarketplacePlugin(request)
|
|
CheckNotImplementedStatus(t, resp)
|
|
require.Nil(t, plugin)
|
|
})
|
|
t.Run("RequirePluginSignature enabled", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.Enable = true
|
|
*cfg.PluginSettings.RequirePluginSignature = true
|
|
})
|
|
manifest, resp := th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
|
|
CheckNotImplementedStatus(t, resp)
|
|
require.Nil(t, manifest)
|
|
|
|
manifest, resp = th.SystemAdminClient.InstallPluginFromUrl("some_url", true)
|
|
CheckNotImplementedStatus(t, resp)
|
|
require.Nil(t, manifest)
|
|
})
|
|
|
|
t.Run("no server", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.EnableMarketplace = true
|
|
*cfg.PluginSettings.MarketplaceUrl = "invalid.com"
|
|
})
|
|
|
|
plugin, resp := th.SystemAdminClient.InstallMarketplacePlugin(request)
|
|
CheckInternalErrorStatus(t, resp)
|
|
require.Nil(t, plugin)
|
|
})
|
|
|
|
t.Run("no permission", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.EnableMarketplace = true
|
|
*cfg.PluginSettings.MarketplaceUrl = "invalid.com"
|
|
})
|
|
|
|
plugin, resp := th.Client.InstallMarketplacePlugin(request)
|
|
CheckForbiddenStatus(t, resp)
|
|
require.Nil(t, plugin)
|
|
})
|
|
|
|
t.Run("plugin not found on the server", func(t *testing.T) {
|
|
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
res.WriteHeader(http.StatusOK)
|
|
json, err := json.Marshal([]*model.MarketplacePlugin{})
|
|
require.NoError(t, err)
|
|
res.Write(json)
|
|
}))
|
|
defer testServer.Close()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.EnableMarketplace = true
|
|
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
|
|
})
|
|
pRequest := &model.InstallMarketplacePluginRequest{Id: "some_plugin_id", Version: "0.0.1"}
|
|
plugin, resp := th.SystemAdminClient.InstallMarketplacePlugin(pRequest)
|
|
CheckInternalErrorStatus(t, resp)
|
|
require.Nil(t, plugin)
|
|
})
|
|
|
|
t.Run("plugin not verified", func(t *testing.T) {
|
|
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
res.WriteHeader(http.StatusOK)
|
|
json, err := json.Marshal([]*model.MarketplacePlugin{samplePlugins[0]})
|
|
require.NoError(t, err)
|
|
res.Write(json)
|
|
}))
|
|
defer testServer.Close()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.EnableMarketplace = true
|
|
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
|
|
*cfg.PluginSettings.AllowInsecureDownloadUrl = true
|
|
})
|
|
pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin_v2", Version: "1.2.2"}
|
|
plugin, resp := th.SystemAdminClient.InstallMarketplacePlugin(pRequest)
|
|
CheckInternalErrorStatus(t, resp)
|
|
require.Nil(t, plugin)
|
|
})
|
|
|
|
t.Run("verify, install and remove plugin", func(t *testing.T) {
|
|
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
res.WriteHeader(http.StatusOK)
|
|
json, err := json.Marshal([]*model.MarketplacePlugin{samplePlugins[1]})
|
|
require.NoError(t, err)
|
|
res.Write(json)
|
|
}))
|
|
defer testServer.Close()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.EnableMarketplace = true
|
|
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
|
|
})
|
|
|
|
pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin_v2", Version: "1.2.3"}
|
|
manifest, resp := th.SystemAdminClient.InstallMarketplacePlugin(pRequest)
|
|
CheckNoError(t, resp)
|
|
require.NotNil(t, manifest)
|
|
require.Equal(t, "testplugin_v2", manifest.Id)
|
|
require.Equal(t, "1.2.3", manifest.Version)
|
|
|
|
filePath := filepath.Join(*th.App.Config().PluginSettings.Directory, "testplugin_v2.0.sig")
|
|
savedSigFile, err := th.App.ReadFile(filePath)
|
|
require.Nil(t, err)
|
|
require.EqualValues(t, sigFile, savedSigFile)
|
|
|
|
ok, resp := th.SystemAdminClient.RemovePlugin(manifest.Id)
|
|
CheckNoError(t, resp)
|
|
assert.True(t, ok)
|
|
exists, err := th.App.FileExists(filePath)
|
|
require.Nil(t, err)
|
|
require.False(t, exists)
|
|
})
|
|
}
|
|
|
|
func findClusterMessages(event string, msgs []*model.ClusterMessage) []*model.ClusterMessage {
|
|
var result []*model.ClusterMessage
|
|
for _, msg := range msgs {
|
|
if msg.Event == event {
|
|
result = append(result, msg)
|
|
}
|
|
}
|
|
return result
|
|
}
|