MM-19606- Rework Prepackaged Plugins (#13449)

* MM-19609 - Add new prepackage configuration settings (#13062)

* Add signatures to the prepackaged plugins (#13138)

* MM-19612 - Support querying local plugin marketplace when upst… (#13250)

* MM-19612 - Support querying local plugin marketplace when upstream unavailable or disabled

* Update translations file

* Fixed comment

* Updated to check EnableRemoteMarketplace setting and LocalOnly to get marketplace plugins

* Fixed unit tests

* Tests cleanup code

* Removed unused error message

* Updated tests

* MM-19614- Updated Marketplace Service error id (#13388)

* [MM-19610] Consume prepackaged plugins (#13005)

* consume prepackaged plugins into memory

* missing i18n

* remove spurious .gitignore changes

* return on failure to install prepackged plugins

* cleanup

* s/plugins/availablePlugins

* whitespace

* don't return extractDir when not needed

* s/plug/plugin

* error on icon, cleanup

* update armored version of testplugin signature

* honour AutomaticPrepackagedPlugins

* document getPrepackagedPlugin

* MM-19613 - Include prepackaged plugins in marketplace results (#13433)

* Added prepackaged plugins to marketplace results

* PR Feedback

* PR Feedback

* Update error where definition

* Removing unnecessary var declaration

* Updated comments

* MM-21263 - Use EnableRemoteMarketplace in marketplace install… (#13438)

* MM-21263 - Use EnableRemoteMarketplace in marketplace install endpoint

* Call updateConfig before calling NewServer in TestHelper

* Added translations

* PR feedback

* Translations

* Feedback

* s/helpers.go/download.go

* Converging env.PrepackagedPlugins

* Initial PR feedback

* Ordered imports properly

* Updated DownloadURL to return slice of bytes

* Fixed method typo

* Fixed logging

* Added read lock for prepackaged plugins list

* PR Feedback

* Added condition to only install prepackaged plugin if it was previously enabled

* Linting

* Updated to check plugin state in config

* Closing filereader

* Only add local label if remote marketplace is enabled

* Updated local tag description

* Fixed tests

Co-authored-by: Ali Farooq <ali.farooq0@pm.me>
Co-authored-by: Shota Gvinepadze <wineson@gmail.com>
Co-authored-by: Jesse Hallam <jesse.hallam@gmail.com>
Co-authored-by: Ben Schumacher <ben.schumacher@mattermost.com>
Этот коммит содержится в:
Maria A Nunez
2020-01-15 13:38:55 -05:00
коммит произвёл GitHub
родитель 508fbf2f53
Коммит 87eb7697f9
29 изменённых файлов: 1554 добавлений и 367 удалений

Просмотреть файл

@@ -9,6 +9,7 @@ import (
"net"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"time"
@@ -63,11 +64,24 @@ func UseTestStore(store store.Store) {
func setupTestHelper(enterprise bool, updateConfig func(*model.Config)) *TestHelper {
testStore.DropAllTables()
tempWorkspace, err := ioutil.TempDir("", "apptest")
if err != nil {
panic(err)
}
memoryStore, err := config.NewMemoryStoreWithOptions(&config.MemoryStoreOptions{IgnoreEnvironmentOverrides: true})
if err != nil {
panic("failed to initialize memory store: " + err.Error())
}
config := memoryStore.Get()
*config.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins")
*config.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp")
if updateConfig != nil {
updateConfig(config)
}
memoryStore.Set(config)
var options []app.Option
options = append(options, app.ConfigStore(memoryStore))
options = append(options, app.StoreOverride(testStore))
@@ -94,9 +108,6 @@ func setupTestHelper(enterprise bool, updateConfig func(*model.Config)) *TestHel
})
prevListenAddress := *th.App.Config().ServiceSettings.ListenAddress
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" })
if updateConfig != nil {
th.App.UpdateConfig(updateConfig)
}
serverErr := th.Server.Start()
if serverErr != nil {
panic(serverErr)
@@ -130,11 +141,7 @@ func setupTestHelper(enterprise bool, updateConfig func(*model.Config)) *TestHel
th.SystemAdminClient = th.CreateClient()
if th.tempWorkspace == "" {
dir, err := ioutil.TempDir("", "apptest")
if err != nil {
panic(err)
}
th.tempWorkspace = dir
th.tempWorkspace = tempWorkspace
}
return th
@@ -580,9 +587,7 @@ func CheckEtag(t *testing.T, data interface{}, resp *model.Response) {
func CheckNoError(t *testing.T, resp *model.Response) {
t.Helper()
if resp.Error != nil {
require.FailNow(t, "Expected no error, got %q", resp.Error.Error())
}
require.Nil(t, resp.Error, "expected no error")
}
func checkHTTPStatus(t *testing.T, resp *model.Response, expectedStatus int, expectError bool) {

Просмотреть файл

@@ -9,10 +9,9 @@ import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"net/url"
"time"
"strconv"
"github.com/mattermost/mattermost-server/v5/mlog"
"github.com/mattermost/mattermost-server/v5/model"
@@ -20,9 +19,6 @@ import (
const (
MAXIMUM_PLUGIN_FILE_SIZE = 50 * 1024 * 1024
// INSTALL_PLUGIN_FROM_URL_HTTP_REQUEST_TIMEOUT defines a high timeout for installing plugins
// from an external URL to avoid slow connections or large plugins from failing to install.
INSTALL_PLUGIN_FROM_URL_HTTP_REQUEST_TIMEOUT = 60 * time.Minute
)
func (api *API) InitPlugin() {
@@ -100,15 +96,15 @@ func installPluginFromUrl(c *Context, w http.ResponseWriter, r *http.Request) {
}
force := r.URL.Query().Get("force") == "true"
downloadUrl := r.URL.Query().Get("plugin_download_url")
downloadURL := r.URL.Query().Get("plugin_download_url")
pluginFile, err := downloadFromUrl(c, downloadUrl)
pluginFileBytes, err := c.App.DownloadFromURL(downloadURL)
if err != nil {
c.Err = err
c.Err = model.NewAppError("installPluginFromUrl", "api.plugin.install.download_failed.app_error", nil, err.Error(), http.StatusBadRequest)
return
}
installPlugin(c, w, pluginFile, force)
installPlugin(c, w, bytes.NewReader(pluginFileBytes), force)
}
func installMarketplacePlugin(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -132,28 +128,13 @@ func installMarketplacePlugin(c *Context, w http.ResponseWriter, r *http.Request
c.Err = model.NewAppError("installMarketplacePlugin", "app.plugin.marketplace_plugin_request.app_error", nil, err.Error(), http.StatusNotImplemented)
return
}
plugin, appErr := c.App.GetMarketplacePlugin(pluginRequest)
manifest, appErr := c.App.InstallMarketplacePlugin(pluginRequest)
if appErr != nil {
c.Err = appErr
return
}
pluginFile, appErr := downloadFromUrl(c, plugin.DownloadURL)
if appErr != nil {
c.Err = appErr
return
}
signature, err := plugin.DecodeSignature()
if err != nil {
c.Err = model.NewAppError("installMarketplacePlugin", "app.plugin.signature_decode.app_error", nil, err.Error(), http.StatusNotImplemented)
return
}
manifest, appErr := c.App.InstallPluginWithSignature(pluginFile, signature)
if appErr != nil {
c.Err = appErr
return
}
w.WriteHeader(http.StatusCreated)
w.Write([]byte(manifest.ToJson()))
}
@@ -347,45 +328,20 @@ func parseMarketplacePluginFilter(u *url.URL) (*model.MarketplacePluginFilter, e
filter := u.Query().Get("filter")
serverVersion := u.Query().Get("server_version")
localOnly, err := strconv.ParseBool(u.Query().Get("local_only"))
if err != nil {
localOnly = false
}
return &model.MarketplacePluginFilter{
Page: page,
PerPage: perPage,
Filter: filter,
ServerVersion: serverVersion,
LocalOnly: localOnly,
}, nil
}
func downloadFromUrl(c *Context, downloadUrl string) (io.ReadSeeker, *model.AppError) {
if !model.IsValidHttpUrl(downloadUrl) {
return nil, model.NewAppError("downloadFromUrl", "api.plugin.install.invalid_url.app_error", nil, "", http.StatusBadRequest)
}
u, err := url.ParseRequestURI(downloadUrl)
if err != nil {
return nil, model.NewAppError("downloadFromUrl", "api.plugin.install.invalid_url.app_error", nil, "", http.StatusBadRequest)
}
if !*c.App.Config().PluginSettings.AllowInsecureDownloadUrl && u.Scheme != "https" {
return nil, model.NewAppError("downloadFromUrl", "api.plugin.install.insecure_url.app_error", nil, "", http.StatusBadRequest)
}
client := c.App.HTTPService.MakeClient(true)
client.Timeout = INSTALL_PLUGIN_FROM_URL_HTTP_REQUEST_TIMEOUT
resp, err := client.Get(downloadUrl)
if err != nil {
return nil, model.NewAppError("downloadFromUrl", "api.plugin.install.download_failed.app_error", nil, err.Error(), http.StatusBadRequest)
}
defer resp.Body.Close()
fileBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, model.NewAppError("downloadFromUrl", "api.plugin.install.reading_stream_failed.app_error", nil, err.Error(), http.StatusBadRequest)
}
return bytes.NewReader(fileBytes), nil
}
func installPlugin(c *Context, w http.ResponseWriter, plugin io.ReadSeeker, force bool) {
manifest, appErr := c.App.InstallPlugin(plugin, force)
if appErr != nil {

Просмотреть файл

@@ -18,8 +18,11 @@ import (
"time"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/plugin"
"github.com/mattermost/mattermost-server/v5/testlib"
"github.com/mattermost/mattermost-server/v5/utils"
"github.com/mattermost/mattermost-server/v5/utils/fileutils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -61,6 +64,10 @@ func TestPlugin(t *testing.T) {
CheckNoError(t, resp)
assert.Equal(t, "testplugin", manifest.Id)
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")
@@ -163,7 +170,7 @@ func TestPlugin(t *testing.T) {
assert.False(t, found)
// Successful activate
ok, resp := th.SystemAdminClient.EnablePlugin(manifest.Id)
ok, resp = th.SystemAdminClient.EnablePlugin(manifest.Id)
CheckNoError(t, resp)
assert.True(t, ok)
@@ -602,7 +609,7 @@ func TestGetInstalledMarketplacePlugins(t *testing.T) {
DownloadURL: "",
Labels: []model.MarketplaceLabel{{
Name: "Local",
Description: "This plugin is not listed in the marketplace but was installed manually",
Description: "This plugin is not listed in the marketplace",
}},
Manifest: manifest,
},
@@ -673,7 +680,7 @@ func TestGetInstalledMarketplacePlugins(t *testing.T) {
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckNoError(t, resp)
newPlugin.InstalledVersion = manifest.Version
newPlugin.InstalledVersion = ""
require.Equal(t, expectedPlugins, plugins)
})
}
@@ -701,7 +708,7 @@ func TestSearchGetMarketplacePlugins(t *testing.T) {
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
require.NoError(t, err)
tarDataV2, err := ioutil.ReadFile(filepath.Join(path, "testpluginv2.tar.gz"))
tarDataV2, err := ioutil.ReadFile(filepath.Join(path, "testplugin2.tar.gz"))
require.NoError(t, err)
t.Run("search installed plugin", func(t *testing.T) {
@@ -730,37 +737,37 @@ func TestSearchGetMarketplacePlugins(t *testing.T) {
manifest, resp := th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
CheckNoError(t, resp)
newPluginV1 := &model.MarketplacePlugin{
plugin1 := &model.MarketplacePlugin{
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
HomepageURL: "",
IconData: "",
DownloadURL: "",
Labels: []model.MarketplaceLabel{{
Name: "Local",
Description: "This plugin is not listed in the marketplace but was installed manually",
Description: "This plugin is not listed in the marketplace",
}},
Manifest: manifest,
},
InstalledVersion: manifest.Version,
}
expectedPlugins := append(samplePlugins, newPluginV1)
expectedPlugins := append(samplePlugins, plugin1)
manifest, resp = th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarDataV2))
CheckNoError(t, resp)
newPluginV2 := &model.MarketplacePlugin{
plugin2 := &model.MarketplacePlugin{
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
HomepageURL: "",
IconData: "",
DownloadURL: "",
Labels: []model.MarketplaceLabel{{
Name: "Local",
Description: "This plugin is not listed in the marketplace but was installed manually",
Description: "This plugin is not listed in the marketplace",
}},
Manifest: manifest,
},
InstalledVersion: manifest.Version,
}
expectedPlugins = append(expectedPlugins, newPluginV2)
expectedPlugins = append(expectedPlugins, plugin2)
sort.SliceStable(expectedPlugins, func(i, j int) bool {
return strings.ToLower(expectedPlugins[i].Manifest.Name) < strings.ToLower(expectedPlugins[j].Manifest.Name)
})
@@ -770,13 +777,13 @@ func TestSearchGetMarketplacePlugins(t *testing.T) {
require.Equal(t, expectedPlugins, plugins)
// Search for plugins from the server
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{Filter: "testplugin_v2"})
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{Filter: "testplugin2"})
CheckNoError(t, resp)
require.Equal(t, []*model.MarketplacePlugin{newPluginV2}, plugins)
require.Equal(t, []*model.MarketplacePlugin{plugin2}, plugins)
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{Filter: "dsgsdg_v2"})
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{Filter: "a second plugin"})
CheckNoError(t, resp)
require.Equal(t, []*model.MarketplacePlugin{newPluginV2}, plugins)
require.Equal(t, []*model.MarketplacePlugin{plugin2}, plugins)
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{Filter: "User Satisfaction Surveys"})
CheckNoError(t, resp)
@@ -785,6 +792,270 @@ func TestSearchGetMarketplacePlugins(t *testing.T) {
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{Filter: "NOFILTER"})
CheckNoError(t, resp)
require.Nil(t, plugins)
// cleanup
ok, resp := th.SystemAdminClient.RemovePlugin(plugin1.Manifest.Id)
CheckNoError(t, resp)
assert.True(t, ok)
ok, resp = th.SystemAdminClient.RemovePlugin(plugin2.Manifest.Id)
CheckNoError(t, resp)
assert.True(t, ok)
})
}
func TestGetLocalPluginInMarketplace(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
samplePlugins := []*model.MarketplacePlugin{
{
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
HomepageURL: "https://example.com/mattermost/mattermost-plugin-nps",
IconData: "https://example.com/icon.svg",
DownloadURL: "www.github.com/example",
Manifest: &model.Manifest{
Id: "testplugin2",
Name: "testplugin2",
Description: "a second plugin",
Version: "1.2.2",
MinServerVersion: "",
},
},
InstalledVersion: "",
},
}
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.Enable = true
*cfg.PluginSettings.EnableMarketplace = true
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
})
t.Run("Get plugins with EnableRemoteMarketplace enabled", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableRemoteMarketplace = true
})
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckNoError(t, resp)
require.Len(t, plugins, len(samplePlugins))
require.Equal(t, samplePlugins, plugins)
})
t.Run("get remote and local plugins", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableRemoteMarketplace = true
*cfg.PluginSettings.EnableUploads = true
})
// Upload one local plugin
path, _ := fileutils.FindDir("tests")
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
require.NoError(t, err)
manifest, resp := th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
CheckNoError(t, resp)
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckNoError(t, resp)
require.Len(t, plugins, 2)
ok, resp := th.SystemAdminClient.RemovePlugin(manifest.Id)
CheckNoError(t, resp)
assert.True(t, ok)
})
t.Run("EnableRemoteMarketplace disabled", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableRemoteMarketplace = false
*cfg.PluginSettings.EnableUploads = true
})
// No marketplace plugins returned
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckNoError(t, resp)
require.Len(t, plugins, 0)
// Upload one local plugin
path, _ := fileutils.FindDir("tests")
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
require.NoError(t, err)
manifest, resp := th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
CheckNoError(t, resp)
newPlugin := &model.MarketplacePlugin{
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
Manifest: manifest,
},
InstalledVersion: manifest.Version,
}
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckNoError(t, resp)
// Only get the local plugins
require.Len(t, plugins, 1)
require.Equal(t, newPlugin, plugins[0])
ok, resp := th.SystemAdminClient.RemovePlugin(manifest.Id)
CheckNoError(t, resp)
assert.True(t, ok)
})
t.Run("local_only true", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableRemoteMarketplace = true
*cfg.PluginSettings.EnableUploads = true
})
// Upload one local plugin
path, _ := fileutils.FindDir("tests")
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
require.NoError(t, err)
manifest, resp := th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
CheckNoError(t, resp)
newPlugin := &model.MarketplacePlugin{
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
Manifest: manifest,
Labels: []model.MarketplaceLabel{{
Name: "Local",
Description: "This plugin is not listed in the marketplace",
}},
},
InstalledVersion: manifest.Version,
}
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{LocalOnly: true})
CheckNoError(t, resp)
require.Len(t, plugins, 1)
require.Equal(t, newPlugin, plugins[0])
ok, resp := th.SystemAdminClient.RemovePlugin(manifest.Id)
CheckNoError(t, resp)
assert.True(t, ok)
})
}
func TestGetPrepackagedPluginInMarketplace(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
marketplacePlugins := []*model.MarketplacePlugin{
{
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
HomepageURL: "https://example.com/mattermost/mattermost-plugin-nps",
IconData: "https://example.com/icon.svg",
DownloadURL: "www.github.com/example",
Manifest: &model.Manifest{
Id: "marketplace.test",
Name: "marketplacetest",
Description: "a marketplace plugin",
Version: "0.1.2",
MinServerVersion: "",
},
},
InstalledVersion: "",
},
}
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusOK)
json, err := json.Marshal([]*model.MarketplacePlugin{marketplacePlugins[0]})
require.NoError(t, err)
res.Write(json)
}))
defer testServer.Close()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.Enable = true
*cfg.PluginSettings.EnableMarketplace = true
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
})
prepackagePlugin := &plugin.PrepackagedPlugin{
Manifest: &model.Manifest{
Version: "0.0.1",
Id: "prepackaged.test",
},
}
env := th.App.GetPluginsEnvironment()
env.SetPrepackagedPlugins([]*plugin.PrepackagedPlugin{prepackagePlugin})
t.Run("get remote and prepackaged plugins", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableRemoteMarketplace = true
*cfg.PluginSettings.EnableUploads = true
})
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckNoError(t, resp)
expectedPlugins := marketplacePlugins
expectedPlugins = append(expectedPlugins, &model.MarketplacePlugin{
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
Manifest: prepackagePlugin.Manifest,
},
})
require.ElementsMatch(t, expectedPlugins, plugins)
require.Len(t, plugins, 2)
})
t.Run("EnableRemoteMarketplace disabled", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableRemoteMarketplace = false
*cfg.PluginSettings.EnableUploads = true
})
// No marketplace plugins returned
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckNoError(t, resp)
// Only returns the prepackaged plugins
require.Len(t, plugins, 1)
require.Equal(t, prepackagePlugin.Manifest, plugins[0].Manifest)
})
t.Run("get prepackaged plugin if newer", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableRemoteMarketplace = true
*cfg.PluginSettings.EnableUploads = true
})
manifest := &model.Manifest{
Version: "1.2.3",
Id: "marketplace.test",
}
newerPrepackagePlugin := &plugin.PrepackagedPlugin{
Manifest: manifest,
}
env := th.App.GetPluginsEnvironment()
env.SetPrepackagedPlugins([]*plugin.PrepackagedPlugin{newerPrepackagePlugin})
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckNoError(t, resp)
require.Len(t, plugins, 1)
require.Equal(t, newerPrepackagePlugin.Manifest, plugins[0].Manifest)
})
}
@@ -797,15 +1068,16 @@ func TestInstallMarketplacePlugin(t *testing.T) {
*cfg.PluginSettings.EnableUploads = true
*cfg.PluginSettings.EnableMarketplace = false
})
path, _ := fileutils.FindDir("tests")
signatureFilename := "testpluginv2.tar.gz.sig"
signatureFilename := "testplugin2.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"))
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin2.tar.gz"))
require.NoError(t, err)
pluginServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusOK)
@@ -820,9 +1092,9 @@ func TestInstallMarketplacePlugin(t *testing.T) {
IconData: "https://example.com/icon.svg",
DownloadURL: pluginServer.URL,
Manifest: &model.Manifest{
Id: "testplugin_v2",
Name: "testplugin_v2",
Description: "dsgsdg_v2",
Id: "testplugin2",
Name: "testplugin2",
Description: "a second plugin",
Version: "1.2.2",
MinServerVersion: "",
},
@@ -835,9 +1107,9 @@ func TestInstallMarketplacePlugin(t *testing.T) {
IconData: "https://example.com/icon.svg",
DownloadURL: pluginServer.URL,
Manifest: &model.Manifest{
Id: "testplugin_v2",
Name: "testplugin_v2",
Description: "dsgsdg_v2",
Id: "testplugin2",
Name: "testplugin2",
Description: "a second plugin",
Version: "1.2.3",
MinServerVersion: "",
},
@@ -846,7 +1118,9 @@ func TestInstallMarketplacePlugin(t *testing.T) {
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
@@ -856,6 +1130,7 @@ func TestInstallMarketplacePlugin(t *testing.T) {
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
@@ -925,7 +1200,7 @@ func TestInstallMarketplacePlugin(t *testing.T) {
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
*cfg.PluginSettings.AllowInsecureDownloadUrl = true
})
pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin_v2", Version: "1.2.2"}
pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin2", Version: "1.2.2"}
plugin, resp := th.SystemAdminClient.InstallMarketplacePlugin(pRequest)
CheckInternalErrorStatus(t, resp)
require.Nil(t, plugin)
@@ -945,6 +1220,7 @@ func TestInstallMarketplacePlugin(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableMarketplace = true
*cfg.PluginSettings.EnableRemoteMarketplace = true
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
})
@@ -953,14 +1229,14 @@ func TestInstallMarketplacePlugin(t *testing.T) {
appErr := th.App.AddPublicKey("pub_key", key)
require.Nil(t, appErr)
pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin_v2", Version: "1.2.3"}
pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin2", 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, "testplugin2", manifest.Id)
require.Equal(t, "1.2.3", manifest.Version)
filePath := filepath.Join(*th.App.Config().PluginSettings.Directory, "testplugin_v2.tar.gz.sig")
filePath := filepath.Join("plugins", "testplugin2.tar.gz.sig")
savedSigFile, err := th.App.ReadFile(filePath)
require.Nil(t, err)
require.EqualValues(t, sigFile, savedSigFile)
@@ -975,6 +1251,221 @@ func TestInstallMarketplacePlugin(t *testing.T) {
appErr = th.App.DeletePublicKey("pub_key")
require.Nil(t, appErr)
})
t.Run("install prepackaged and remote plugins through marketplace", func(t *testing.T) {
prepackagedPluginsDir := "prepackaged_plugins"
os.RemoveAll(prepackagedPluginsDir)
err := os.Mkdir(prepackagedPluginsDir, os.ModePerm)
require.NoError(t, err)
defer os.RemoveAll(prepackagedPluginsDir)
prepackagedPluginsDir, found := fileutils.FindDir(prepackagedPluginsDir)
require.True(t, found, "failed to find prepackaged plugins directory")
err = utils.CopyFile(filepath.Join(path, "testplugin.tar.gz"), filepath.Join(prepackagedPluginsDir, "testplugin.tar.gz"))
require.NoError(t, err)
err = utils.CopyFile(filepath.Join(path, "testplugin.tar.gz.asc"), filepath.Join(prepackagedPluginsDir, "testplugin.tar.gz.sig"))
require.NoError(t, err)
th := SetupConfig(func(cfg *model.Config) {
// Disable auto-installing prepackaged plugins
*cfg.PluginSettings.AutomaticPrepackagedPlugins = false
}).InitBasic()
defer th.TearDown()
pluginSignatureFile, err := os.Open(filepath.Join(path, "testplugin.tar.gz.asc"))
require.Nil(t, err)
pluginSignatureData, err := ioutil.ReadAll(pluginSignatureFile)
require.Nil(t, err)
key, err := os.Open(filepath.Join(path, "development-private-key.asc"))
require.NoError(t, err)
appErr := th.App.AddPublicKey("pub_key", key)
require.Nil(t, appErr)
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
serverVersion := req.URL.Query().Get("server_version")
require.NotEmpty(t, serverVersion)
require.Equal(t, model.CurrentVersion, serverVersion)
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.EnableRemoteMarketplace = false
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
*cfg.PluginSettings.AllowInsecureDownloadUrl = false
})
env := th.App.GetPluginsEnvironment()
pluginsResp, resp := th.SystemAdminClient.GetPlugins()
CheckNoError(t, resp)
require.Len(t, pluginsResp.Active, 0)
require.Len(t, pluginsResp.Inactive, 0)
// Should fail to install unknown prepackaged plugin
pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin", Version: "0.0.2"}
manifest, resp := th.SystemAdminClient.InstallMarketplacePlugin(pRequest)
CheckInternalErrorStatus(t, resp)
require.Nil(t, manifest)
plugins := env.PrepackagedPlugins()
require.Len(t, plugins, 1)
require.Equal(t, "testplugin", plugins[0].Manifest.Id)
require.Equal(t, pluginSignatureData, plugins[0].Signature)
pluginsResp, resp = th.SystemAdminClient.GetPlugins()
CheckNoError(t, resp)
require.Len(t, pluginsResp.Active, 0)
require.Len(t, pluginsResp.Inactive, 0)
pRequest = &model.InstallMarketplacePluginRequest{Id: "testplugin", Version: "0.0.1"}
manifest1, resp := th.SystemAdminClient.InstallMarketplacePlugin(pRequest)
CheckNoError(t, resp)
require.NotNil(t, manifest1)
require.Equal(t, "testplugin", manifest1.Id)
require.Equal(t, "0.0.1", manifest1.Version)
pluginsResp, resp = th.SystemAdminClient.GetPlugins()
CheckNoError(t, resp)
require.Len(t, pluginsResp.Active, 0)
require.Equal(t, pluginsResp.Inactive, []*model.PluginInfo{{
Manifest: *manifest1,
}})
// Try to install remote marketplace plugin
pRequest = &model.InstallMarketplacePluginRequest{Id: "testplugin2", Version: "1.2.3"}
manifest, resp = th.SystemAdminClient.InstallMarketplacePlugin(pRequest)
CheckInternalErrorStatus(t, resp)
require.Nil(t, manifest)
// Enable remote marketplace
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableMarketplace = true
*cfg.PluginSettings.EnableRemoteMarketplace = true
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
*cfg.PluginSettings.AllowInsecureDownloadUrl = true
})
pRequest = &model.InstallMarketplacePluginRequest{Id: "testplugin2", Version: "1.2.3"}
manifest2, resp := th.SystemAdminClient.InstallMarketplacePlugin(pRequest)
CheckNoError(t, resp)
require.NotNil(t, manifest2)
require.Equal(t, "testplugin2", manifest2.Id)
require.Equal(t, "1.2.3", manifest2.Version)
pluginsResp, resp = th.SystemAdminClient.GetPlugins()
CheckNoError(t, resp)
require.Len(t, pluginsResp.Active, 0)
require.ElementsMatch(t, pluginsResp.Inactive, []*model.PluginInfo{
{
Manifest: *manifest1,
},
{
Manifest: *manifest2,
},
})
// Clean up
ok, resp := th.SystemAdminClient.RemovePlugin(manifest1.Id)
CheckNoError(t, resp)
assert.True(t, ok)
ok, resp = th.SystemAdminClient.RemovePlugin(manifest2.Id)
CheckNoError(t, resp)
assert.True(t, ok)
appErr = th.App.DeletePublicKey("pub_key")
require.Nil(t, appErr)
})
t.Run("missing prepackaged and remote plugin signatures", func(t *testing.T) {
prepackagedPluginsDir := "prepackaged_plugins"
os.RemoveAll(prepackagedPluginsDir)
err := os.Mkdir(prepackagedPluginsDir, os.ModePerm)
require.NoError(t, err)
defer os.RemoveAll(prepackagedPluginsDir)
prepackagedPluginsDir, found := fileutils.FindDir(prepackagedPluginsDir)
require.True(t, found, "failed to find prepackaged plugins directory")
err = utils.CopyFile(filepath.Join(path, "testplugin.tar.gz"), filepath.Join(prepackagedPluginsDir, "testplugin.tar.gz"))
require.NoError(t, err)
th := SetupConfig(func(cfg *model.Config) {
// Disable auto-installing prepackged plugins
*cfg.PluginSettings.AutomaticPrepackagedPlugins = false
}).InitBasic()
defer th.TearDown()
key, err := os.Open(filepath.Join(path, "development-private-key.asc"))
require.NoError(t, err)
appErr := th.App.AddPublicKey("pub_key", key)
require.Nil(t, appErr)
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
serverVersion := req.URL.Query().Get("server_version")
require.NotEmpty(t, serverVersion)
require.Equal(t, model.CurrentVersion, serverVersion)
mPlugins := []*model.MarketplacePlugin{samplePlugins[0]}
require.Empty(t, mPlugins[0].Signature)
res.WriteHeader(http.StatusOK)
json, err := json.Marshal(mPlugins)
require.NoError(t, err)
res.Write(json)
}))
defer testServer.Close()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableMarketplace = true
*cfg.PluginSettings.EnableRemoteMarketplace = true
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
*cfg.PluginSettings.AllowInsecureDownloadUrl = true
})
env := th.App.GetPluginsEnvironment()
plugins := env.PrepackagedPlugins()
require.Len(t, plugins, 1)
require.Equal(t, "testplugin", plugins[0].Manifest.Id)
require.Empty(t, plugins[0].Signature)
pluginsResp, resp := th.SystemAdminClient.GetPlugins()
CheckNoError(t, resp)
require.Len(t, pluginsResp.Active, 0)
require.Len(t, pluginsResp.Inactive, 0)
pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin", Version: "0.0.1"}
manifest, resp := th.SystemAdminClient.InstallMarketplacePlugin(pRequest)
CheckInternalErrorStatus(t, resp)
require.Nil(t, manifest)
pluginsResp, resp = th.SystemAdminClient.GetPlugins()
CheckNoError(t, resp)
require.Len(t, pluginsResp.Active, 0)
require.Len(t, pluginsResp.Inactive, 0)
pRequest = &model.InstallMarketplacePluginRequest{Id: "testplugin2", Version: "1.2.3"}
manifest, resp = th.SystemAdminClient.InstallMarketplacePlugin(pRequest)
CheckInternalErrorStatus(t, resp)
require.Nil(t, manifest)
pluginsResp, resp = th.SystemAdminClient.GetPlugins()
CheckNoError(t, resp)
require.Len(t, pluginsResp.Active, 0)
require.Len(t, pluginsResp.Inactive, 0)
// Clean up
appErr = th.App.DeletePublicKey("pub_key")
require.Nil(t, appErr)
})
}
func findClusterMessages(event string, msgs []*model.ClusterMessage) []*model.ClusterMessage {

44
app/download.go Обычный файл
Просмотреть файл

@@ -0,0 +1,44 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"io/ioutil"
"net/url"
"time"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/pkg/errors"
)
const (
// HTTP_REQUEST_TIMEOUT defines a high timeout for downloading large files
// from an external URL to avoid slow connections from failing to install.
HTTP_REQUEST_TIMEOUT = 1 * time.Hour
)
func (a *App) DownloadFromURL(downloadURL string) ([]byte, error) {
if !model.IsValidHttpUrl(downloadURL) {
return nil, errors.Errorf("invalid url %s", downloadURL)
}
u, err := url.ParseRequestURI(downloadURL)
if err != nil {
return nil, errors.Errorf("failed to parse url %s", downloadURL)
}
if !*a.Config().PluginSettings.AllowInsecureDownloadUrl && u.Scheme != "https" {
return nil, errors.Errorf("insecure url not allowed %s", downloadURL)
}
client := a.HTTPService.MakeClient(true)
client.Timeout = HTTP_REQUEST_TIMEOUT
resp, err := client.Get(downloadURL)
if err != nil {
return nil, errors.Wrapf(err, "failed to fetch from %s", downloadURL)
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}

Просмотреть файл

@@ -37,11 +37,21 @@ func setupTestHelper(enterprise bool, tb testing.TB) *TestHelper {
store := mainHelper.GetStore()
store.DropAllTables()
tempWorkspace, err := ioutil.TempDir("", "apptest")
if err != nil {
panic(err)
}
memoryStore, err := config.NewMemoryStoreWithOptions(&config.MemoryStoreOptions{IgnoreEnvironmentOverrides: true})
if err != nil {
panic("failed to initialize memory store: " + err.Error())
}
config := memoryStore.Get()
*config.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins")
*config.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp")
memoryStore.Set(config)
var options []Option
options = append(options, ConfigStore(memoryStore))
options = append(options, StoreOverride(mainHelper.Store))
@@ -88,18 +98,9 @@ func setupTestHelper(enterprise bool, tb testing.TB) *TestHelper {
}
if th.tempWorkspace == "" {
dir, err := ioutil.TempDir("", "apptest")
if err != nil {
panic(err)
}
th.tempWorkspace = dir
th.tempWorkspace = tempWorkspace
}
pluginDir := filepath.Join(th.tempWorkspace, "plugins")
webappDir := filepath.Join(th.tempWorkspace, "webapp")
th.App.InitPlugins(pluginDir, webappDir)
return th
}

Просмотреть файл

@@ -4,6 +4,10 @@
package app
import (
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
@@ -16,9 +20,14 @@ import (
"github.com/mattermost/mattermost-server/v5/services/filesstore"
"github.com/mattermost/mattermost-server/v5/services/marketplace"
"github.com/mattermost/mattermost-server/v5/utils/fileutils"
"github.com/blang/semver"
svg "github.com/h2non/go-is-svg"
"github.com/pkg/errors"
)
const prepackagedPluginsDir = "prepackaged_plugins"
type pluginSignaturePath struct {
pluginId string
path string
@@ -163,32 +172,9 @@ func (a *App) InitPlugins(pluginDir, webappPluginDir string) {
mlog.Error("Failed to sync plugins from the file store", mlog.Err(err))
}
prepackagedPluginsDir, found := fileutils.FindDir("prepackaged_plugins")
if found {
if err := filepath.Walk(prepackagedPluginsDir, func(walkPath string, info os.FileInfo, err error) error {
if !strings.HasSuffix(walkPath, ".tar.gz") {
return nil
}
fileReader, err := os.Open(walkPath)
if err != nil {
mlog.Error("Failed to open prepackaged plugin", mlog.Err(err), mlog.String("path", walkPath))
return nil
}
defer fileReader.Close()
mlog.Debug("Installing prepackaged plugin", mlog.String("path", walkPath))
_, appErr := a.installPluginLocally(fileReader, nil, installPluginLocallyOnlyIfNewOrUpgrade)
if appErr != nil {
mlog.Error("Failed to unpack prepackaged plugin", mlog.Err(appErr), mlog.String("path", walkPath))
}
return nil
}); err != nil {
mlog.Error("Failed to complete unpacking prepackaged plugins", mlog.Err(err))
}
}
plugins := a.processPrepackagedPlugins(prepackagedPluginsDir)
pluginsEnvironment = a.GetPluginsEnvironment()
pluginsEnvironment.SetPrepackagedPlugins(plugins)
// Sync plugin active state when config changes. Also notify plugins.
a.Srv.PluginsLock.Lock()
@@ -319,7 +305,7 @@ func (a *App) EnablePlugin(id string) *model.AppError {
return model.NewAppError("EnablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
}
plugins, err := pluginsEnvironment.Available()
availablePlugins, err := pluginsEnvironment.Available()
if err != nil {
return model.NewAppError("EnablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -327,7 +313,7 @@ func (a *App) EnablePlugin(id string) *model.AppError {
id = strings.ToLower(id)
var manifest *model.Manifest
for _, p := range plugins {
for _, p := range availablePlugins {
if p.Manifest.Id == id {
manifest = p.Manifest
break
@@ -361,7 +347,7 @@ func (a *App) DisablePlugin(id string) *model.AppError {
return model.NewAppError("DisablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
}
plugins, err := pluginsEnvironment.Available()
availablePlugins, err := pluginsEnvironment.Available()
if err != nil {
return model.NewAppError("DisablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -369,7 +355,7 @@ func (a *App) DisablePlugin(id string) *model.AppError {
id = strings.ToLower(id)
var manifest *model.Manifest
for _, p := range plugins {
for _, p := range availablePlugins {
if p.Manifest.Id == id {
manifest = p.Manifest
break
@@ -423,94 +409,35 @@ func (a *App) GetPlugins() (*model.PluginsResponse, *model.AppError) {
return resp, nil
}
// GetMarketplacePlugin returns plugin from marketplace-server
func (a *App) GetMarketplacePlugin(request *model.InstallMarketplacePluginRequest) (*model.BaseMarketplacePlugin, *model.AppError) {
marketplaceClient, err := marketplace.NewClient(
*a.Config().PluginSettings.MarketplaceUrl,
a.HTTPService,
)
if err != nil {
return nil, model.NewAppError("GetMarketplacePlugin", "app.plugin.marketplace_client.app_error", nil, err.Error(), http.StatusInternalServerError)
}
filter := &model.MarketplacePluginFilter{Filter: request.Id, ServerVersion: model.CurrentVersion}
plugin, err := marketplaceClient.GetPlugin(filter, request.Version)
if err != nil {
return nil, model.NewAppError("GetMarketplacePlugin", "app.plugin.marketplace_plugins.not_found.app_error", nil, err.Error(), http.StatusInternalServerError)
}
return plugin, nil
}
// GetMarketplacePlugins returns a list of plugins from the marketplace-server,
// and plugins that are installed locally.
func (a *App) GetMarketplacePlugins(filter *model.MarketplacePluginFilter) ([]*model.MarketplacePlugin, *model.AppError) {
plugins := map[string]*model.MarketplacePlugin{}
if *a.Config().PluginSettings.EnableRemoteMarketplace && !filter.LocalOnly {
p, appErr := a.getRemotePlugins(filter)
if appErr != nil {
return nil, appErr
}
plugins = p
}
appErr := a.mergePrepackagedPlugins(plugins)
if appErr != nil {
return nil, appErr
}
appErr = a.mergeLocalPlugins(plugins)
if appErr != nil {
return nil, appErr
}
// Filter plugins.
var result []*model.MarketplacePlugin
pluginSet := map[string]bool{}
pluginsEnvironment := a.GetPluginsEnvironment()
if pluginsEnvironment == nil {
return nil, model.NewAppError("GetMarketplacePlugins", "app.plugin.config.app_error", nil, "", http.StatusInternalServerError)
}
marketplaceClient, err := marketplace.NewClient(
*a.Config().PluginSettings.MarketplaceUrl,
a.HTTPService,
)
if err != nil {
return nil, model.NewAppError("GetMarketplacePlugins", "app.plugin.marketplace_client.app_error", nil, err.Error(), http.StatusInternalServerError)
}
// Fetch all plugins from marketplace.
marketplacePlugins, err := marketplaceClient.GetPlugins(&model.MarketplacePluginFilter{
PerPage: -1,
ServerVersion: model.CurrentVersion,
})
if err != nil {
return nil, model.NewAppError("GetMarketplacePlugins", "app.plugin.marketplace_plugins.app_error", nil, err.Error(), http.StatusInternalServerError)
}
for _, p := range marketplacePlugins {
if p.Manifest == nil || !pluginMatchesFilter(p.Manifest, filter.Filter) {
continue
for _, p := range plugins {
if pluginMatchesFilter(p.Manifest, filter.Filter) {
result = append(result, p)
}
marketplacePlugin := &model.MarketplacePlugin{
BaseMarketplacePlugin: p,
}
var manifest *model.Manifest
if manifest, err = pluginsEnvironment.GetManifest(p.Manifest.Id); err != nil && err != plugin.ErrNotFound {
return nil, model.NewAppError("GetMarketplacePlugins", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError)
} else if err == nil {
// Plugin is installed.
marketplacePlugin.InstalledVersion = manifest.Version
}
pluginSet[p.Manifest.Id] = true
result = append(result, marketplacePlugin)
}
// Include all other installed plugins.
plugins, err := pluginsEnvironment.Available()
if err != nil {
return nil, model.NewAppError("GetMarketplacePlugins", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError)
}
for _, plugin := range plugins {
if plugin.Manifest == nil || pluginSet[plugin.Manifest.Id] || !pluginMatchesFilter(plugin.Manifest, filter.Filter) {
continue
}
result = append(result, &model.MarketplacePlugin{
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
// Labels should not (yet) be localized as the labels sent by the Marketplace are not (yet) localizable.
Labels: []model.MarketplaceLabel{{
Name: "Local",
Description: "This plugin is not listed in the marketplace but was installed manually",
}},
Manifest: plugin.Manifest,
},
InstalledVersion: plugin.Manifest.Version,
})
}
// Sort result alphabetically.
@@ -521,6 +448,166 @@ func (a *App) GetMarketplacePlugins(filter *model.MarketplacePluginFilter) ([]*m
return result, nil
}
// getPrepackagedPlugin returns a pre-packaged plugin.
func (a *App) getPrepackagedPlugin(pluginId, version string) (*plugin.PrepackagedPlugin, *model.AppError) {
pluginsEnvironment := a.GetPluginsEnvironment()
if pluginsEnvironment == nil {
return nil, model.NewAppError("getPrepackagedPlugin", "app.plugin.config.app_error", nil, "plugin environment is nil", http.StatusInternalServerError)
}
prepackagedPlugins := pluginsEnvironment.PrepackagedPlugins()
for _, p := range prepackagedPlugins {
if p.Manifest.Id == pluginId && p.Manifest.Version == version {
return p, nil
}
}
return nil, model.NewAppError("getPrepackagedPlugin", "app.plugin.marketplace_plugins.not_found.app_error", nil, "", http.StatusInternalServerError)
}
// getRemoteMarketplacePlugin returns plugin from marketplace-server.
func (a *App) getRemoteMarketplacePlugin(pluginId, version string) (*model.BaseMarketplacePlugin, *model.AppError) {
marketplaceClient, err := marketplace.NewClient(
*a.Config().PluginSettings.MarketplaceUrl,
a.HTTPService,
)
if err != nil {
return nil, model.NewAppError("GetMarketplacePlugin", "app.plugin.marketplace_client.app_error", nil, err.Error(), http.StatusInternalServerError)
}
filter := &model.MarketplacePluginFilter{Filter: pluginId, ServerVersion: model.CurrentVersion}
plugin, err := marketplaceClient.GetPlugin(filter, version)
if err != nil {
return nil, model.NewAppError("GetMarketplacePlugin", "app.plugin.marketplace_plugins.not_found.app_error", nil, err.Error(), http.StatusInternalServerError)
}
return plugin, nil
}
func (a *App) getRemotePlugins(filter *model.MarketplacePluginFilter) (map[string]*model.MarketplacePlugin, *model.AppError) {
result := map[string]*model.MarketplacePlugin{}
pluginsEnvironment := a.GetPluginsEnvironment()
if pluginsEnvironment == nil {
return nil, model.NewAppError("getRemotePlugins", "app.plugin.config.app_error", nil, "", http.StatusInternalServerError)
}
marketplaceClient, err := marketplace.NewClient(
*a.Config().PluginSettings.MarketplaceUrl,
a.HTTPService,
)
if err != nil {
return nil, model.NewAppError("getRemotePlugins", "app.plugin.marketplace_client.app_error", nil, err.Error(), http.StatusInternalServerError)
}
// Fetch all plugins from marketplace.
marketplacePlugins, err := marketplaceClient.GetPlugins(&model.MarketplacePluginFilter{
PerPage: -1,
ServerVersion: model.CurrentVersion,
})
if err != nil {
return nil, model.NewAppError("getRemotePlugins", "app.plugin.marketplace_client.failed_to_fetch", nil, err.Error(), http.StatusInternalServerError)
}
for _, p := range marketplacePlugins {
if p.Manifest == nil {
continue
}
result[p.Manifest.Id] = &model.MarketplacePlugin{BaseMarketplacePlugin: p}
}
return result, nil
}
// mergePrepackagedPlugins merges pre-packaged plugins to remote marketplace plugins list.
func (a *App) mergePrepackagedPlugins(remoteMarketplacePlugins map[string]*model.MarketplacePlugin) *model.AppError {
pluginsEnvironment := a.GetPluginsEnvironment()
if pluginsEnvironment == nil {
return model.NewAppError("mergePrepackagedPlugins", "app.plugin.config.app_error", nil, "", http.StatusInternalServerError)
}
for _, prepackaged := range pluginsEnvironment.PrepackagedPlugins() {
if prepackaged.Manifest == nil {
continue
}
prepackagedMarketplace := &model.MarketplacePlugin{
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
Manifest: prepackaged.Manifest,
},
}
// If not available in marketplace, add the prepackaged
if remoteMarketplacePlugins[prepackaged.Manifest.Id] == nil {
remoteMarketplacePlugins[prepackaged.Manifest.Id] = prepackagedMarketplace
continue
}
// If available in the markteplace, only overwrite if newer.
prepackagedVersion, err := semver.Parse(prepackaged.Manifest.Version)
if err != nil {
return model.NewAppError("mergePrepackagedPlugins", "app.plugin.invalid_version.app_error", nil, "", http.StatusBadRequest)
}
marketplacePlugin := remoteMarketplacePlugins[prepackaged.Manifest.Id]
marketplaceVersion, err := semver.Parse(marketplacePlugin.Manifest.Version)
if err != nil {
return model.NewAppError("mergePrepackagedPlugins", "app.plugin.invalid_version.app_error", nil, "", http.StatusBadRequest)
}
if prepackagedVersion.GT(marketplaceVersion) {
remoteMarketplacePlugins[prepackaged.Manifest.Id] = prepackagedMarketplace
}
}
return nil
}
// mergeLocalPlugins merges locally installed plugins to remote marketplace plugins list.
func (a *App) mergeLocalPlugins(remoteMarketplacePlugins map[string]*model.MarketplacePlugin) *model.AppError {
pluginsEnvironment := a.GetPluginsEnvironment()
if pluginsEnvironment == nil {
return model.NewAppError("GetMarketplacePlugins", "app.plugin.config.app_error", nil, "", http.StatusInternalServerError)
}
localPlugins, err := pluginsEnvironment.Available()
if err != nil {
return model.NewAppError("GetMarketplacePlugins", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError)
}
for _, plugin := range localPlugins {
if plugin.Manifest == nil {
continue
}
if remoteMarketplacePlugins[plugin.Manifest.Id] != nil {
// Remote plugin is installed.
remoteMarketplacePlugins[plugin.Manifest.Id].InstalledVersion = plugin.Manifest.Version
continue
}
var labels []model.MarketplaceLabel
if *a.Config().PluginSettings.EnableRemoteMarketplace {
// Labels should not (yet) be localized as the labels sent by the Marketplace are not (yet) localizable.
labels = append(labels, model.MarketplaceLabel{
Name: "Local",
Description: "This plugin is not listed in the marketplace",
})
}
remoteMarketplacePlugins[plugin.Manifest.Id] = &model.MarketplacePlugin{
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
Labels: labels,
Manifest: plugin.Manifest,
},
InstalledVersion: plugin.Manifest.Version,
}
}
return nil
}
func pluginMatchesFilter(manifest *model.Manifest, filter string) bool {
filter = strings.TrimSpace(strings.ToLower(filter))
@@ -600,6 +687,10 @@ func (a *App) getPluginsFromFolder() (map[string]*pluginSignaturePath, *model.Ap
return nil, model.NewAppError("getPluginsFromDir", "app.plugin.sync.list_filestore.app_error", nil, appErr.Error(), http.StatusInternalServerError)
}
return getPluginsFromFilePaths(fileStorePaths), nil
}
func getPluginsFromFilePaths(fileStorePaths []string) map[string]*pluginSignaturePath {
pluginSignaturePathMap := make(map[string]*pluginSignaturePath)
for _, path := range fileStorePaths {
if strings.HasSuffix(path, ".tar.gz") {
@@ -623,5 +714,121 @@ func (a *App) getPluginsFromFolder() (map[string]*pluginSignaturePath, *model.Ap
}
}
return pluginSignaturePathMap, nil
return pluginSignaturePathMap
}
func (a *App) processPrepackagedPlugins(pluginsDir string) []*plugin.PrepackagedPlugin {
prepackagedPluginsDir, found := fileutils.FindDir(pluginsDir)
if !found {
return nil
}
fileStorePaths := []string{}
err := filepath.Walk(prepackagedPluginsDir, func(walkPath string, info os.FileInfo, err error) error {
fileStorePaths = append(fileStorePaths, walkPath)
return nil
})
if err != nil {
mlog.Error("Failed to walk prepackaged plugins", mlog.Err(err))
return nil
}
pluginSignaturePathMap := getPluginsFromFilePaths(fileStorePaths)
plugins := make([]*plugin.PrepackagedPlugin, 0, len(pluginSignaturePathMap))
for _, pluginPaths := range pluginSignaturePathMap {
plugin, err := a.processPrepackagedPlugin(pluginPaths)
if err != nil {
mlog.Error("Failed to install prepackaged plugin", mlog.String("path", pluginPaths.path), mlog.Err(err))
continue
}
plugins = append(plugins, plugin)
}
return plugins
}
// processPrepackagedPlugin will return the prepackaged plugin metadata and will also
// install the prepackaged plugin if it had been previously enabled and AutomaticPrepackagedPlugins is true.
func (a *App) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (*plugin.PrepackagedPlugin, error) {
mlog.Debug("Processing prepackaged plugin", mlog.String("path", pluginPath.path))
fileReader, err := os.Open(pluginPath.path)
if err != nil {
return nil, errors.Wrapf(err, "Failed to open prepackaged plugin %s", pluginPath.path)
}
tmpDir, err := ioutil.TempDir("", "plugintmp")
if err != nil {
return nil, errors.Wrap(err, "Failed to create temp dir plugintmp")
}
defer os.RemoveAll(tmpDir)
plugin, pluginDir, err := getPrepackagedPlugin(pluginPath, fileReader, tmpDir)
if err != nil {
return nil, errors.Wrapf(err, "Failed to get prepackaged plugin %s", pluginPath.path)
}
// Skip installing the plugin at all if automatic prepackaged plugins is disabled
if !*a.Config().PluginSettings.AutomaticPrepackagedPlugins {
return plugin, nil
}
// Skip installing if the plugin is has not been previously enabled.
pluginState := a.Config().PluginSettings.PluginStates[plugin.Manifest.Id]
if pluginState == nil || !pluginState.Enable {
return plugin, nil
}
mlog.Debug("Installing prepackaged plugin", mlog.String("path", pluginPath.path))
if _, err := a.installExtractedPlugin(plugin.Manifest, pluginDir, installPluginLocallyOnlyIfNewOrUpgrade); err != nil {
return nil, errors.Wrapf(err, "Failed to install extracted prepackaged plugin %s", pluginPath.path)
}
return plugin, nil
}
// getPrepackagedPlugin builds a PrepackagedPlugin from the plugin at the given path, additionally returning the directory in which it was extracted.
func getPrepackagedPlugin(pluginPath *pluginSignaturePath, pluginFile io.ReadSeeker, tmpDir string) (*plugin.PrepackagedPlugin, string, error) {
manifest, pluginDir, appErr := extractPlugin(pluginFile, tmpDir)
if appErr != nil {
return nil, "", errors.Wrapf(appErr, "Failed to extract plugin with path %s", pluginPath.path)
}
plugin := new(plugin.PrepackagedPlugin)
plugin.Manifest = manifest
plugin.Path = pluginPath.path
if pluginPath.signaturePath != "" {
sig := pluginPath.signaturePath
sigReader, sigErr := os.Open(sig)
if sigErr != nil {
return nil, "", errors.Wrapf(sigErr, "Failed to open prepackaged plugin signature %s", sig)
}
bytes, sigErr := ioutil.ReadAll(sigReader)
if sigErr != nil {
return nil, "", errors.Wrapf(sigErr, "Failed to read prepackaged plugin signature %s", sig)
}
plugin.Signature = bytes
}
if manifest.IconPath != "" {
iconData, err := getIcon(manifest.IconPath)
if err != nil {
return nil, "", errors.Wrapf(err, "Failed to read icon at %s", manifest.IconPath)
}
plugin.IconData = iconData
}
return plugin, pluginDir, nil
}
func getIcon(iconPath string) (string, error) {
icon, err := ioutil.ReadFile(iconPath)
if err != nil {
return "", errors.Wrapf(err, "failed to open icon at path %s", iconPath)
}
if !svg.Is(icon) {
return "", errors.Wrapf(err, "icon is not svg %s", iconPath)
}
return fmt.Sprintf("data:image/svg+xml;base64,%s", base64.StdEncoding.EncodeToString(icon)), nil
}

Просмотреть файл

@@ -37,6 +37,7 @@
package app
import (
"bytes"
"fmt"
"io"
"io/ioutil"
@@ -45,6 +46,8 @@ import (
"path/filepath"
"github.com/blang/semver"
"github.com/pkg/errors"
"github.com/mattermost/mattermost-server/v5/mlog"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/plugin"
@@ -169,6 +172,61 @@ func (a *App) installPlugin(pluginFile, signature io.ReadSeeker, installationStr
return manifest, nil
}
// InstallMarketplacePlugin installs a plugin listed in the marketplace server. It will get the plugin bundle
// from the prepackaged folder, if available, or remotely if EnableRemoteMarketplace is true.
func (a *App) InstallMarketplacePlugin(request *model.InstallMarketplacePluginRequest) (*model.Manifest, *model.AppError) {
var pluginFile, signatureFile io.ReadSeeker
prepackagedPlugin, appErr := a.getPrepackagedPlugin(request.Id, request.Version)
if appErr != nil && appErr.Id != "app.plugin.marketplace_plugins.not_found.app_error" {
return nil, appErr
}
if prepackagedPlugin != nil {
fileReader, err := os.Open(prepackagedPlugin.Path)
if err != nil {
err = errors.Wrapf(err, "failed to open prepackaged plugin %s", prepackagedPlugin.Path)
return nil, model.NewAppError("InstallMarketplacePlugin", "app.plugin.install_marketplace_plugin.app_error", nil, err.Error(), http.StatusInternalServerError)
}
defer fileReader.Close()
pluginFile = fileReader
signatureFile = bytes.NewReader(prepackagedPlugin.Signature)
}
if *a.Config().PluginSettings.EnableRemoteMarketplace && pluginFile == nil {
var plugin *model.BaseMarketplacePlugin
plugin, appErr = a.getRemoteMarketplacePlugin(request.Id, request.Version)
if appErr != nil {
return nil, appErr
}
downloadedPluginBytes, err := a.DownloadFromURL(plugin.DownloadURL)
if err != nil {
return nil, model.NewAppError("InstallMarketplacePlugin", "app.plugin.install_marketplace_plugin.app_error", nil, err.Error(), http.StatusInternalServerError)
}
signature, err := plugin.DecodeSignature()
if err != nil {
return nil, model.NewAppError("InstallMarketplacePlugin", "app.plugin.signature_decode.app_error", nil, err.Error(), http.StatusNotImplemented)
}
pluginFile = bytes.NewReader(downloadedPluginBytes)
signatureFile = signature
}
if pluginFile == nil {
return nil, model.NewAppError("InstallMarketplacePlugin", "app.plugin.marketplace_plugins.not_found.app_error", nil, "", http.StatusInternalServerError)
}
if signatureFile == nil {
return nil, model.NewAppError("InstallMarketplacePlugin", "app.plugin.marketplace_plugins.signature_not_found.app_error", nil, "", http.StatusInternalServerError)
}
manifest, appErr := a.InstallPluginWithSignature(pluginFile, signatureFile)
if appErr != nil {
return nil, appErr
}
return manifest, nil
}
type pluginInstallationStrategy int
const (
@@ -185,6 +243,7 @@ func (a *App) installPluginLocally(pluginFile, signature io.ReadSeeker, installa
if pluginsEnvironment == nil {
return nil, model.NewAppError("installPluginLocally", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
}
// verify signature
if signature != nil {
if err := a.VerifyPlugin(pluginFile, signature); err != nil {
@@ -198,33 +257,55 @@ func (a *App) installPluginLocally(pluginFile, signature io.ReadSeeker, installa
}
defer os.RemoveAll(tmpDir)
pluginFile.Seek(0, 0)
if err = utils.ExtractTarGz(pluginFile, tmpDir); err != nil {
return nil, model.NewAppError("installPluginLocally", "app.plugin.extract.app_error", nil, err.Error(), http.StatusBadRequest)
manifest, pluginDir, appErr := extractPlugin(pluginFile, tmpDir)
if appErr != nil {
return nil, appErr
}
tmpPluginDir := tmpDir
dir, err := ioutil.ReadDir(tmpDir)
manifest, appErr = a.installExtractedPlugin(manifest, pluginDir, installationStrategy)
if appErr != nil {
return nil, appErr
}
return manifest, nil
}
func extractPlugin(pluginFile io.ReadSeeker, extractDir string) (*model.Manifest, string, *model.AppError) {
pluginFile.Seek(0, 0)
if err := utils.ExtractTarGz(pluginFile, extractDir); err != nil {
return nil, "", model.NewAppError("extractPlugin", "app.plugin.extract.app_error", nil, err.Error(), http.StatusBadRequest)
}
dir, err := ioutil.ReadDir(extractDir)
if err != nil {
return nil, model.NewAppError("installPluginLocally", "app.plugin.filesystem.app_error", nil, err.Error(), http.StatusInternalServerError)
return nil, "", model.NewAppError("extractPlugin", "app.plugin.filesystem.app_error", nil, err.Error(), http.StatusInternalServerError)
}
if len(dir) == 1 && dir[0].IsDir() {
tmpPluginDir = filepath.Join(tmpPluginDir, dir[0].Name())
extractDir = filepath.Join(extractDir, dir[0].Name())
}
manifest, _, err := model.FindManifest(tmpPluginDir)
manifest, _, err := model.FindManifest(extractDir)
if err != nil {
return nil, model.NewAppError("installPluginLocally", "app.plugin.manifest.app_error", nil, err.Error(), http.StatusBadRequest)
return nil, "", model.NewAppError("extractPlugin", "app.plugin.manifest.app_error", nil, err.Error(), http.StatusBadRequest)
}
if !plugin.IsValidId(manifest.Id) {
return nil, model.NewAppError("installPluginLocally", "app.plugin.invalid_id.app_error", map[string]interface{}{"Min": plugin.MinIdLength, "Max": plugin.MaxIdLength, "Regex": plugin.ValidIdRegex}, "", http.StatusBadRequest)
return nil, "", model.NewAppError("extractPlugin", "app.plugin.invalid_id.app_error", map[string]interface{}{"Min": plugin.MinIdLength, "Max": plugin.MaxIdLength, "Regex": plugin.ValidIdRegex}, "", http.StatusBadRequest)
}
return manifest, extractDir, nil
}
func (a *App) installExtractedPlugin(manifest *model.Manifest, fromPluginDir string, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) {
pluginsEnvironment := a.GetPluginsEnvironment()
if pluginsEnvironment == nil {
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
}
bundles, err := pluginsEnvironment.Available()
if err != nil {
return nil, model.NewAppError("installPluginLocally", "app.plugin.install.app_error", nil, err.Error(), http.StatusInternalServerError)
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.install.app_error", nil, err.Error(), http.StatusInternalServerError)
}
// Check for plugins installed with the same ID.
@@ -239,7 +320,7 @@ func (a *App) installPluginLocally(pluginFile, signature io.ReadSeeker, installa
if existingManifest != nil {
// Return an error if already installed and strategy disallows installation.
if installationStrategy == installPluginLocallyOnlyIfNew {
return nil, model.NewAppError("installPluginLocally", "app.plugin.install_id.app_error", nil, "", http.StatusBadRequest)
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.install_id.app_error", nil, "", http.StatusBadRequest)
}
// Skip installation if already installed and newer.
@@ -248,12 +329,12 @@ func (a *App) installPluginLocally(pluginFile, signature io.ReadSeeker, installa
version, err = semver.Parse(manifest.Version)
if err != nil {
return nil, model.NewAppError("installPluginLocally", "app.plugin.invalid_version.app_error", nil, "", http.StatusBadRequest)
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.invalid_version.app_error", nil, "", http.StatusBadRequest)
}
existingVersion, err = semver.Parse(existingManifest.Version)
if err != nil {
return nil, model.NewAppError("installPluginLocally", "app.plugin.invalid_version.app_error", nil, "", http.StatusBadRequest)
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.invalid_version.app_error", nil, "", http.StatusBadRequest)
}
if version.LTE(existingVersion) {
@@ -265,37 +346,37 @@ func (a *App) installPluginLocally(pluginFile, signature io.ReadSeeker, installa
// Otherwise remove the existing installation prior to install below.
mlog.Debug("Removing existing installation of plugin before local install", mlog.String("plugin_id", existingManifest.Id), mlog.String("version", existingManifest.Version))
if err := a.removePluginLocally(existingManifest.Id); err != nil {
return nil, model.NewAppError("installPluginLocally", "app.plugin.install_id_failed_remove.app_error", nil, "", http.StatusBadRequest)
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.install_id_failed_remove.app_error", nil, "", http.StatusBadRequest)
}
}
pluginPath := filepath.Join(*a.Config().PluginSettings.Directory, manifest.Id)
err = utils.CopyDir(tmpPluginDir, pluginPath)
err = utils.CopyDir(fromPluginDir, pluginPath)
if err != nil {
return nil, model.NewAppError("installPluginLocally", "app.plugin.mvdir.app_error", nil, err.Error(), http.StatusInternalServerError)
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.mvdir.app_error", nil, err.Error(), http.StatusInternalServerError)
}
// Flag plugin locally as managed by the filestore.
f, err := os.Create(filepath.Join(pluginPath, managedPluginFileName))
if err != nil {
return nil, model.NewAppError("installPluginLocally", "app.plugin.flag_managed.app_error", nil, err.Error(), http.StatusInternalServerError)
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.flag_managed.app_error", nil, err.Error(), http.StatusInternalServerError)
}
f.Close()
if manifest.HasWebapp() {
updatedManifest, err := pluginsEnvironment.UnpackWebappBundle(manifest.Id)
if err != nil {
return nil, model.NewAppError("installPluginLocally", "app.plugin.webapp_bundle.app_error", nil, err.Error(), http.StatusInternalServerError)
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.webapp_bundle.app_error", nil, err.Error(), http.StatusInternalServerError)
}
manifest = updatedManifest
}
// Activate plugin if it was previously activated.
// Activate the plugin if enabled.
pluginState := a.Config().PluginSettings.PluginStates[manifest.Id]
if pluginState != nil && pluginState.Enable {
updatedManifest, _, err := pluginsEnvironment.Activate(manifest.Id)
if err != nil {
return nil, model.NewAppError("installPluginLocally", "app.plugin.restart.app_error", nil, err.Error(), http.StatusInternalServerError)
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.restart.app_error", nil, err.Error(), http.StatusInternalServerError)
}
manifest = updatedManifest
}

Просмотреть файл

@@ -22,6 +22,7 @@ import (
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/plugin"
"github.com/mattermost/mattermost-server/v5/utils"
"github.com/mattermost/mattermost-server/v5/utils/fileutils"
)
@@ -485,108 +486,316 @@ func TestPluginSync(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
os.MkdirAll("./test-plugins", os.ModePerm)
defer os.RemoveAll("./test-plugins")
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.Enable = true
*cfg.PluginSettings.Directory = "./test-plugins"
*cfg.PluginSettings.ClientDirectory = "./test-client-plugins"
*cfg.PluginSettings.RequirePluginSignature = false
testCase.ConfigFunc(cfg)
})
th.App.UpdateConfig(testCase.ConfigFunc)
env, err := plugin.NewEnvironment(th.App.NewPluginAPI, "./test-plugins", "./test-client-plugins", th.App.Log)
require.NoError(t, err)
th.App.SetPluginsEnvironment(env)
env := th.App.GetPluginsEnvironment()
require.NotNil(t, env)
// New bundle in the file store case
path, _ := fileutils.FindDir("tests")
fileReader, err := os.Open(filepath.Join(path, "testplugin.tar.gz"))
require.NoError(t, err)
defer fileReader.Close()
_, appErr := th.App.WriteFile(fileReader, th.App.getBundleStorePath("testplugin"))
checkNoError(t, appErr)
t.Run("new bundle in the file store", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.RequirePluginSignature = false
})
appErr = th.App.SyncPlugins()
checkNoError(t, appErr)
fileReader, err := os.Open(filepath.Join(path, "testplugin.tar.gz"))
require.NoError(t, err)
defer fileReader.Close()
// Check if installed
pluginStatus, err := env.Statuses()
require.Nil(t, err)
require.Len(t, pluginStatus, 1)
require.Equal(t, pluginStatus[0].PluginId, "testplugin")
_, appErr := th.App.WriteFile(fileReader, th.App.getBundleStorePath("testplugin"))
checkNoError(t, appErr)
// Bundle removed from the file store case
appErr = th.App.RemoveFile(th.App.getBundleStorePath("testplugin"))
checkNoError(t, appErr)
appErr = th.App.SyncPlugins()
checkNoError(t, appErr)
appErr = th.App.SyncPlugins()
checkNoError(t, appErr)
// Check if removed
pluginStatus, err = env.Statuses()
require.Nil(t, err)
require.Empty(t, pluginStatus)
// RequirePluginSignature = true case
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.RequirePluginSignature = true
// Check if installed
pluginStatus, err := env.Statuses()
require.Nil(t, err)
require.Len(t, pluginStatus, 1)
require.Equal(t, pluginStatus[0].PluginId, "testplugin")
})
pluginFileReader, err := os.Open(filepath.Join(path, "testplugin.tar.gz"))
require.NoError(t, err)
defer pluginFileReader.Close()
_, appErr = th.App.WriteFile(pluginFileReader, th.App.getBundleStorePath("testplugin"))
checkNoError(t, appErr)
// no signature
appErr = th.App.SyncPlugins()
checkNoError(t, appErr)
pluginStatus, err = env.Statuses()
require.Nil(t, err)
require.Empty(t, pluginStatus)
// Wrong signature
signatureFileReader, err := os.Open(filepath.Join(path, "testpluginv2.tar.gz.sig"))
require.NoError(t, err)
defer signatureFileReader.Close()
filePath := fmt.Sprintf("%s.sig", th.App.getBundleStorePath("testplugin"))
_, appErr = th.App.WriteFile(signatureFileReader, filePath)
checkNoError(t, appErr)
t.Run("bundle removed from the file store", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.RequirePluginSignature = false
})
appErr = th.App.SyncPlugins()
checkNoError(t, appErr)
appErr := th.App.RemoveFile(th.App.getBundleStorePath("testplugin"))
checkNoError(t, appErr)
pluginStatus, err = env.Statuses()
require.Nil(t, err)
require.Empty(t, pluginStatus)
appErr = th.App.SyncPlugins()
checkNoError(t, appErr)
// Correct signature
key, err := os.Open(filepath.Join(path, "development-private-key.asc"))
require.NoError(t, err)
appErr = th.App.AddPublicKey("pub_key", key)
checkNoError(t, appErr)
// Check if removed
pluginStatus, err := env.Statuses()
require.Nil(t, err)
require.Empty(t, pluginStatus)
})
signatureFileReader, err = os.Open(filepath.Join(path, "testplugin.tar.gz.sig"))
require.NoError(t, err)
defer signatureFileReader.Close()
filePath = fmt.Sprintf("%s.sig", th.App.getBundleStorePath("testplugin"))
_, appErr = th.App.WriteFile(signatureFileReader, filePath)
checkNoError(t, appErr)
t.Run("plugin signatures required, no signature", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.RequirePluginSignature = true
})
appErr = th.App.SyncPlugins()
checkNoError(t, appErr)
pluginFileReader, err := os.Open(filepath.Join(path, "testplugin.tar.gz"))
require.NoError(t, err)
defer pluginFileReader.Close()
_, appErr := th.App.WriteFile(pluginFileReader, th.App.getBundleStorePath("testplugin"))
checkNoError(t, appErr)
pluginStatus, err = env.Statuses()
require.Nil(t, err)
require.Len(t, pluginStatus, 1)
require.Equal(t, pluginStatus[0].PluginId, "testplugin")
appErr = th.App.SyncPlugins()
checkNoError(t, appErr)
pluginStatus, err := env.Statuses()
require.Nil(t, err)
require.Len(t, pluginStatus, 0)
})
appErr = th.App.DeletePublicKey("pub_key")
checkNoError(t, appErr)
t.Run("plugin signatures required, wrong signature", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.RequirePluginSignature = true
})
appErr = th.App.RemovePlugin("testplugin")
checkNoError(t, appErr)
signatureFileReader, err := os.Open(filepath.Join(path, "testplugin2.tar.gz.sig"))
require.NoError(t, err)
defer signatureFileReader.Close()
_, appErr := th.App.WriteFile(signatureFileReader, th.App.getSignatureStorePath("testplugin"))
checkNoError(t, appErr)
appErr = th.App.SyncPlugins()
checkNoError(t, appErr)
pluginStatus, err := env.Statuses()
require.Nil(t, err)
require.Len(t, pluginStatus, 0)
})
t.Run("plugin signatures required, correct signature", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.RequirePluginSignature = true
})
key, err := os.Open(filepath.Join(path, "development-private-key.asc"))
require.NoError(t, err)
appErr := th.App.AddPublicKey("pub_key", key)
checkNoError(t, appErr)
signatureFileReader, err := os.Open(filepath.Join(path, "testplugin.tar.gz.sig"))
require.NoError(t, err)
defer signatureFileReader.Close()
_, appErr = th.App.WriteFile(signatureFileReader, th.App.getSignatureStorePath("testplugin"))
checkNoError(t, appErr)
appErr = th.App.SyncPlugins()
checkNoError(t, appErr)
pluginStatus, err := env.Statuses()
require.Nil(t, err)
require.Len(t, pluginStatus, 1)
require.Equal(t, pluginStatus[0].PluginId, "testplugin")
appErr = th.App.DeletePublicKey("pub_key")
checkNoError(t, appErr)
appErr = th.App.RemovePlugin("testplugin")
checkNoError(t, appErr)
})
})
}
}
func TestProcessPrepackagedPlugins(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
testsPath, _ := fileutils.FindDir("tests")
prepackagedPluginsPath := filepath.Join(testsPath, prepackagedPluginsDir)
fileErr := os.Mkdir(prepackagedPluginsPath, os.ModePerm)
require.NoError(t, fileErr)
defer os.RemoveAll(prepackagedPluginsPath)
prepackagedPluginsDir, found := fileutils.FindDir(prepackagedPluginsPath)
require.True(t, found, "failed to find prepackaged plugins directory")
testPluginPath := filepath.Join(testsPath, "testplugin.tar.gz")
fileErr = utils.CopyFile(testPluginPath, filepath.Join(prepackagedPluginsDir, "testplugin.tar.gz"))
require.NoError(t, fileErr)
t.Run("automatic, enabled plugin, no signature", func(t *testing.T) {
// Install the plugin and enable
pluginBytes, err := ioutil.ReadFile(testPluginPath)
require.NoError(t, err)
require.NotNil(t, pluginBytes)
manifest, appErr := th.App.installPluginLocally(bytes.NewReader(pluginBytes), nil, installPluginLocallyAlways)
require.Nil(t, appErr)
require.Equal(t, "testplugin", manifest.Id)
env := th.App.GetPluginsEnvironment()
activatedManifest, activated, err := env.Activate(manifest.Id)
require.NoError(t, err)
require.True(t, activated)
require.Equal(t, manifest, activatedManifest)
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.Enable = true
*cfg.PluginSettings.AutomaticPrepackagedPlugins = true
})
plugins := th.App.processPrepackagedPlugins(prepackagedPluginsDir)
require.Len(t, plugins, 1)
require.Equal(t, plugins[0].Manifest.Id, "testplugin")
require.Empty(t, plugins[0].Signature, 0)
pluginStatus, err := env.Statuses()
require.NoError(t, err)
require.Len(t, pluginStatus, 1)
require.Equal(t, pluginStatus[0].PluginId, "testplugin")
appErr = th.App.RemovePlugin("testplugin")
checkNoError(t, appErr)
pluginStatus, err = env.Statuses()
require.NoError(t, err)
require.Len(t, pluginStatus, 0)
})
t.Run("automatic, not enabled plugin", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.Enable = true
*cfg.PluginSettings.AutomaticPrepackagedPlugins = true
})
env := th.App.GetPluginsEnvironment()
plugins := th.App.processPrepackagedPlugins(prepackagedPluginsDir)
require.Len(t, plugins, 1)
require.Equal(t, plugins[0].Manifest.Id, "testplugin")
require.Empty(t, plugins[0].Signature, 0)
pluginStatus, err := env.Statuses()
require.NoError(t, err)
require.Empty(t, pluginStatus, 0)
})
t.Run("automatic, multiple plugins with signatures, not enabled", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.Enable = true
*cfg.PluginSettings.AutomaticPrepackagedPlugins = true
})
env := th.App.GetPluginsEnvironment()
// Add signature
testPluginSignaturePath := filepath.Join(testsPath, "testplugin.tar.gz.sig")
err := utils.CopyFile(testPluginSignaturePath, filepath.Join(prepackagedPluginsDir, "testplugin.tar.gz.sig"))
require.NoError(t, err)
// Add second plugin
testPlugin2Path := filepath.Join(testsPath, "testplugin2.tar.gz")
err = utils.CopyFile(testPlugin2Path, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz"))
require.NoError(t, err)
testPlugin2SignaturePath := filepath.Join(testsPath, "testplugin2.tar.gz.sig")
err = utils.CopyFile(testPlugin2SignaturePath, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz.sig"))
require.NoError(t, err)
plugins := th.App.processPrepackagedPlugins(prepackagedPluginsDir)
require.Len(t, plugins, 2)
require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[0].Manifest.Id)
require.NotEmpty(t, plugins[0].Signature)
require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[1].Manifest.Id)
require.NotEmpty(t, plugins[1].Signature)
pluginStatus, err := env.Statuses()
require.NoError(t, err)
require.Len(t, pluginStatus, 0)
})
t.Run("automatic, multiple plugins with signatures, one enabled", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.Enable = true
*cfg.PluginSettings.AutomaticPrepackagedPlugins = true
})
env := th.App.GetPluginsEnvironment()
// Add signature
testPluginSignaturePath := filepath.Join(testsPath, "testplugin.tar.gz.sig")
err := utils.CopyFile(testPluginSignaturePath, filepath.Join(prepackagedPluginsDir, "testplugin.tar.gz.sig"))
require.NoError(t, err)
// Install first plugin and enable
pluginBytes, err := ioutil.ReadFile(testPluginPath)
require.NoError(t, err)
require.NotNil(t, pluginBytes)
manifest, appErr := th.App.installPluginLocally(bytes.NewReader(pluginBytes), nil, installPluginLocallyAlways)
require.Nil(t, appErr)
require.Equal(t, "testplugin", manifest.Id)
activatedManifest, activated, err := env.Activate(manifest.Id)
require.NoError(t, err)
require.True(t, activated)
require.Equal(t, manifest, activatedManifest)
// Add second plugin
testPlugin2Path := filepath.Join(testsPath, "testplugin2.tar.gz")
err = utils.CopyFile(testPlugin2Path, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz"))
require.NoError(t, err)
testPlugin2SignaturePath := filepath.Join(testsPath, "testplugin2.tar.gz.sig")
err = utils.CopyFile(testPlugin2SignaturePath, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz.sig"))
require.NoError(t, err)
plugins := th.App.processPrepackagedPlugins(prepackagedPluginsDir)
require.Len(t, plugins, 2)
require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[0].Manifest.Id)
require.NotEmpty(t, plugins[0].Signature)
require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[1].Manifest.Id)
require.NotEmpty(t, plugins[1].Signature)
pluginStatus, err := env.Statuses()
require.NoError(t, err)
require.Len(t, pluginStatus, 1)
require.Equal(t, pluginStatus[0].PluginId, "testplugin")
appErr = th.App.RemovePlugin("testplugin")
checkNoError(t, appErr)
pluginStatus, err = env.Statuses()
require.NoError(t, err)
require.Len(t, pluginStatus, 0)
})
t.Run("non-automatic, multiple plugins", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.Enable = true
*cfg.PluginSettings.AutomaticPrepackagedPlugins = false
})
env := th.App.GetPluginsEnvironment()
testPlugin2Path := filepath.Join(testsPath, "testplugin2.tar.gz")
err := utils.CopyFile(testPlugin2Path, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz"))
require.NoError(t, err)
testPlugin2SignaturePath := filepath.Join(testsPath, "testplugin2.tar.gz.sig")
err = utils.CopyFile(testPlugin2SignaturePath, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz.sig"))
require.NoError(t, err)
plugins := th.App.processPrepackagedPlugins(prepackagedPluginsDir)
require.Len(t, plugins, 2)
require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[0].Manifest.Id)
require.NotEmpty(t, plugins[0].Signature)
require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[1].Manifest.Id)
require.NotEmpty(t, plugins[1].Signature)
pluginStatus, err := env.Statuses()
require.NoError(t, err)
require.Len(t, pluginStatus, 0)
})
}

Просмотреть файл

@@ -67,6 +67,7 @@ endif
mkdir -p tmpprepackaged
@cd tmpprepackaged && for plugin_package in $(PLUGIN_PACKAGES) ; do \
curl -O -L https://plugins-store.test.mattermost.com/release/$$plugin_package.tar.gz; \
curl -O -L https://plugins-store.test.mattermost.com/release/$$plugin_package.tar.gz.sig; \
done
@# ----- PLATFORM SPECIFIC -----
@@ -83,6 +84,7 @@ endif
@# Strip and prepackage plugins
@for plugin_package in $(PLUGIN_PACKAGES) ; do \
cat tmpprepackaged/$$plugin_package.tar.gz | gunzip | tar --wildcards --delete "*windows*" --delete "*linux*" | gzip > $(DIST_PATH)/prepackaged_plugins/$$plugin_package.tar.gz; \
cp tmpprepackaged/$$plugin_package.tar.gz.sig $(DIST_PATH)/prepackaged_plugins; \
done
@# Package
tar -C dist -czf $(DIST_PATH)-$(BUILD_TYPE_NAME)-osx-amd64.tar.gz mattermost
@@ -103,6 +105,7 @@ endif
@# Strip and prepackage plugins
@for plugin_package in $(PLUGIN_PACKAGES) ; do \
cat tmpprepackaged/$$plugin_package.tar.gz | gunzip | tar --wildcards --delete "*darwin*" --delete "*linux*" | gzip > $(DIST_PATH)/prepackaged_plugins/$$plugin_package.tar.gz; \
cp tmpprepackaged/$$plugin_package.tar.gz.sig $(DIST_PATH)/prepackaged_plugins; \
done
@# Package
cd $(DIST_ROOT) && zip -9 -r -q -l mattermost-$(BUILD_TYPE_NAME)-windows-amd64.zip mattermost && cd ..
@@ -123,6 +126,7 @@ endif
@# Strip and prepackage plugins
@for plugin_package in $(PLUGIN_PACKAGES) ; do \
cat tmpprepackaged/$$plugin_package.tar.gz | gunzip | tar --wildcards --delete "*windows*" --delete "*darwin*" | gzip > $(DIST_PATH)/prepackaged_plugins/$$plugin_package.tar.gz; \
cp tmpprepackaged/$$plugin_package.tar.gz.sig $(DIST_PATH)/prepackaged_plugins; \
done
@# Package
tar -C dist -czf $(DIST_PATH)-$(BUILD_TYPE_NAME)-linux-amd64.tar.gz mattermost

1
go.mod
Просмотреть файл

@@ -26,6 +26,7 @@ require (
github.com/gorilla/schema v1.1.0
github.com/gorilla/websocket v1.4.1
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c
github.com/hako/durafmt v0.0.0-20190612201238-650ed9f29a84
github.com/hashicorp/go-hclog v0.9.2
github.com/hashicorp/go-immutable-radix v1.1.0 // indirect

2
go.sum
Просмотреть файл

@@ -155,6 +155,8 @@ github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:Fecb
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c h1:fEE5/5VNnYUoBOj2I9TP8Jc+a7lge3QWn9DKE7NCwfc=
github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c/go.mod h1:ObS/W+h8RYb1Y7fYivughjxojTmIu5iAIjSrSLCLeqE=
github.com/hako/durafmt v0.0.0-20190612201238-650ed9f29a84 h1:RvcDqcKLua4b/jtXez7ZVe9s6Iq5N6ujVevqY4FBQmM=
github.com/hako/durafmt v0.0.0-20190612201238-650ed9f29a84/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=

Просмотреть файл

@@ -1572,18 +1572,6 @@
"id": "api.plugin.install.download_failed.app_error",
"translation": "An error occurred while downloading the plugin."
},
{
"id": "api.plugin.install.insecure_url.app_error",
"translation": "An insecure url was given to download the plugin. Please provide a secure url or enable PluginSettings.AllowInsecureDownloadUrl in your configuration."
},
{
"id": "api.plugin.install.invalid_url.app_error",
"translation": "An invalid url was given to download the plugin."
},
{
"id": "api.plugin.install.reading_stream_failed.app_error",
"translation": "An error ocurred reading the plugin file stream."
},
{
"id": "api.plugin.upload.array.app_error",
"translation": "File array is empty in multipart/form request"
@@ -3584,7 +3572,7 @@
},
{
"id": "app.plugin.extract.app_error",
"translation": "Encountered error extracting plugin"
"translation": "An error occurred extracting the plugin bundle."
},
{
"id": "app.plugin.filesystem.app_error",
@@ -3622,6 +3610,10 @@
"id": "app.plugin.install_id_failed_remove.app_error",
"translation": "Unable to install plugin. A plugin with the same ID is already installed and failed to be removed."
},
{
"id": "app.plugin.install_marketplace_plugin.app_error",
"translation": "Failed to install marketplace plugin."
},
{
"id": "app.plugin.invalid_id.app_error",
"translation": "Plugin Id must be at least {{.Min}} characters, at most {{.Max}} characters and match {{.Regex}}."
@@ -3638,6 +3630,10 @@
"id": "app.plugin.marketplace_client.app_error",
"translation": "Failed to create marketplace client."
},
{
"id": "app.plugin.marketplace_client.failed_to_fetch",
"translation": "Failed to get plugins from the marketplace server."
},
{
"id": "app.plugin.marketplace_disabled.app_error",
"translation": "Marketplace has been disabled. Please check your logs for details."
@@ -3646,14 +3642,14 @@
"id": "app.plugin.marketplace_plugin_request.app_error",
"translation": "Failed to decode the marketplace plugin request."
},
{
"id": "app.plugin.marketplace_plugins.app_error",
"translation": "Failed to get plugins from the marketplace server."
},
{
"id": "app.plugin.marketplace_plugins.not_found.app_error",
"translation": "Could not find the requested marketplace plugin."
},
{
"id": "app.plugin.marketplace_plugins.signature_not_found.app_error",
"translation": "Could not find the requested marketplace plugin signature."
},
{
"id": "app.plugin.marshal.app_error",
"translation": "Failed to marshal marketplace plugins."

Просмотреть файл

@@ -2265,18 +2265,20 @@ type PluginState struct {
}
type PluginSettings struct {
Enable *bool
EnableUploads *bool `restricted:"true"`
AllowInsecureDownloadUrl *bool `restricted:"true"`
EnableHealthCheck *bool `restricted:"true"`
Directory *string `restricted:"true"`
ClientDirectory *string `restricted:"true"`
Plugins map[string]map[string]interface{}
PluginStates map[string]*PluginState
EnableMarketplace *bool
RequirePluginSignature *bool
MarketplaceUrl *string
SignaturePublicKeyFiles []string
Enable *bool
EnableUploads *bool `restricted:"true"`
AllowInsecureDownloadUrl *bool `restricted:"true"`
EnableHealthCheck *bool `restricted:"true"`
Directory *string `restricted:"true"`
ClientDirectory *string `restricted:"true"`
Plugins map[string]map[string]interface{}
PluginStates map[string]*PluginState
EnableMarketplace *bool
EnableRemoteMarketplace *bool
AutomaticPrepackagedPlugins *bool
RequirePluginSignature *bool
MarketplaceUrl *string
SignaturePublicKeyFiles []string
}
func (s *PluginSettings) SetDefaults(ls LogSettings) {
@@ -2321,6 +2323,14 @@ func (s *PluginSettings) SetDefaults(ls LogSettings) {
s.EnableMarketplace = NewBool(PLUGIN_SETTINGS_DEFAULT_ENABLE_MARKETPLACE)
}
if s.EnableRemoteMarketplace == nil {
s.EnableRemoteMarketplace = NewBool(true)
}
if s.AutomaticPrepackagedPlugins == nil {
s.AutomaticPrepackagedPlugins = NewBool(true)
}
if s.MarketplaceUrl == nil || *s.MarketplaceUrl == "" || *s.MarketplaceUrl == PLUGIN_SETTINGS_OLD_MARKETPLACE_URL {
s.MarketplaceUrl = NewString(PLUGIN_SETTINGS_DEFAULT_MARKETPLACE_URL)
}

Просмотреть файл

@@ -78,6 +78,7 @@ type MarketplacePluginFilter struct {
PerPage int
Filter string
ServerVersion string
LocalOnly bool
}
// ApplyToURL modifies the given url to include query string parameters for the request.
@@ -89,6 +90,7 @@ func (filter *MarketplacePluginFilter) ApplyToURL(u *url.URL) {
}
q.Add("filter", filter.Filter)
q.Add("server_version", filter.ServerVersion)
q.Add("local_only", strconv.FormatBool(filter.LocalOnly))
u.RawQuery = q.Encode()
}

Просмотреть файл

@@ -36,17 +36,27 @@ type registeredPlugin struct {
supervisor *supervisor
}
// PrepackagedPlugin is a plugin prepackaged with the server and found on startup.
type PrepackagedPlugin struct {
Path string
IconData string
Manifest *model.Manifest
Signature []byte
}
// Environment represents the execution environment of active plugins.
//
// It is meant for use by the Mattermost server to manipulate, interact with and report on the set
// of active plugins.
type Environment struct {
registeredPlugins sync.Map
pluginHealthCheckJob *PluginHealthCheckJob
logger *mlog.Logger
newAPIImpl apiImplCreatorFunc
pluginDir string
webappPluginDir string
registeredPlugins sync.Map
pluginHealthCheckJob *PluginHealthCheckJob
logger *mlog.Logger
newAPIImpl apiImplCreatorFunc
pluginDir string
webappPluginDir string
prepackagedPlugins []*PrepackagedPlugin
prepackagedPluginsLock sync.RWMutex
}
func NewEnvironment(newAPIImpl apiImplCreatorFunc, pluginDir string, webappPluginDir string, logger *mlog.Logger) (*Environment, error) {
@@ -87,6 +97,15 @@ func (env *Environment) Available() ([]*model.BundleInfo, error) {
return scanSearchPath(env.pluginDir)
}
// Returns a list of prepackaged plugins available in the local prepackaged_plugins folder.
// The list content is immutable and should not be modified.
func (env *Environment) PrepackagedPlugins() []*PrepackagedPlugin {
env.prepackagedPluginsLock.RLock()
defer env.prepackagedPluginsLock.RUnlock()
return env.prepackagedPlugins
}
// Returns a list of all currently active plugins within the environment.
func (env *Environment) Active() []*model.BundleInfo {
activePlugins := []*model.BundleInfo{}
@@ -439,6 +458,13 @@ func (env *Environment) RunMultiPluginHook(hookRunnerFunc func(hooks Hooks) bool
})
}
// SetPrepackagedPlugins saves prepackaged plugins in the environment.
func (env *Environment) SetPrepackagedPlugins(plugins []*PrepackagedPlugin) {
env.prepackagedPluginsLock.Lock()
env.prepackagedPlugins = plugins
env.prepackagedPluginsLock.Unlock()
}
func newRegisteredPlugin(bundle *model.BundleInfo) *registeredPlugin {
state := model.PluginStateNotRunning
return &registeredPlugin{failTimeStamps: []time.Time{}, State: state, BundleInfo: bundle}

Двоичные данные
tests/testplugin.tar.gz

Двоичный файл не отображается.

Просмотреть файл

@@ -1,14 +1,15 @@
-----BEGIN PGP SIGNATURE-----
-----BEGIN PGP ARMORED FILE-----
Comment: Use "gpg --dearmor" for unpacking
iQGzBAABCAAdFiEE8/rOReDeZCyL1qjmTHxlYsGSzB8FAl26+QIACgkQTHxlYsGS
zB9SFwwAmexJelfpTRjnABJKWnFKuGOTNosVNOWqE+k//n1PV3H3uCzsluh/GWIb
p5Nk6+UJieNvyAaQhIT5+3Rv0HkYvz4N15813SvQ8KOL8vE7oRFHttsZHpDKTpk1
fQrd9JISMdViVj4x8VltgYkUIJDmULPfF9/m1OGV/BsWuHmmaEp3yw+KLdhL1j3U
LlfW453nHvXrGT2rO3l+KDIVMt4zG6LVmL1C29yWtBplKxjgXKg/EbPcI31/8D9X
QoIz9fUIFzqt4L1IfJhL6NlUiDc1iSKf4STiU2CzmHdKqRD9FdwgzZlG5P/xxCgu
BVNy/c9musZbK9LtapncC/S62uwZzH5naTrCDs4F1VE2P5gjU9iv3AXVaeT4hLuI
EbluXzmAt9hjox2s0O0X6EgW68FbUVX9bvu1tY9sUvWlXNCL2hrybF3YS3cZ2wGx
rmdWmwktXDpJ8iN/b1+quw0CVC7myAefPK0U8356tktZHwcQEp2fmrF4PqX2qfJf
cDzhj2yg
=J9kV
-----END PGP SIGNATURE-----
iQGzBAABCAAdFiEE8/rOReDeZCyL1qjmTHxlYsGSzB8FAl3vHFQACgkQTHxlYsGS
zB933gv5AcDL/MW5/fwsLnhWW9plSCbRTMOckt94r0ASOKRzPP7ngEzGtkCFEIGQ
bN/T60s9uADuIRHEFhmtZvG2MOg9Z688BxiDpyyTx7ENZH0zzPnULEg5hqf7DJqK
Q8IdPSNE4wk4gvJLdrYIYUkQT4TkQonmBbIHvj3wWKaXTheDnUNp90pOiTszKubK
ghpAj6ZAlRK60HuMMF7v0RIhqLKYKUGUWvzClKEUZlAKlqTK++FTM0Zot/7KTLDz
KEv8gf6xe4oL7vSQBoZDa3Vvcn6tPF0gvwg2DrITT3MG2Rmgpbc74iUXWqoZ+XQp
zf1P+vxbfbyKMYZ4tERlKfwnl6Dbz5vBvlz/U+ZYBmwuw4wD2DvdrecPHj1KcBNy
2Auap8qhMQcZvYV7D/qnYL8/QU4roQdg4z+G/S0vQTlK5izpzY+/M8ofZHtjDdWD
cD3w1cvwBFKn9EBJ1HA4/7FEHUzFrh/kWRw2PpUjkydCur2rHqecpRXBqS+Haz7J
OT9k54ab
=ywEK
-----END PGP ARMORED FILE-----

Двоичные данные
tests/testplugin.tar.gz.sig

Двоичный файл не отображается.

Двоичные данные
tests/testplugin2.tar.gz Обычный файл

Двоичный файл не отображается.

Двоичные данные
tests/testplugin2.tar.gz.sig Обычный файл

Двоичный файл не отображается.

Двоичные данные
tests/testpluginv2.tar.gz

Двоичный файл не отображается.

Двоичные данные
tests/testpluginv2.tar.gz.sig

Двоичный файл не отображается.

12
vendor/github.com/h2non/go-is-svg/.editorconfig сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,12 @@
root = true
[*]
indent_style = tabs
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

7
vendor/github.com/h2non/go-is-svg/.gitignore сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,7 @@
/bimg
/bundle
bin
/*.jpg
/*.png
/*.webp
/fixtures/*_out.*

23
vendor/github.com/h2non/go-is-svg/.travis.yml сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,23 @@
language: go
go:
- 1.5
- 1.6
- 1.7
- tip
before_install:
- go get github.com/nbio/st
- go get -u -v github.com/axw/gocov/gocov
- go get -u -v github.com/mattn/goveralls
- go get -u -v github.com/golang/lint/golint
script:
- diff -u <(echo -n) <(gofmt -s -d ./)
- diff -u <(echo -n) <(go vet ./...)
- diff -u <(echo -n) <(golint ./...)
- go test -v -race ./...
- go test -v -race -covermode=atomic -coverprofile=coverage.out
after_success:
- goveralls -coverprofile=coverage.out -service=travis-ci

24
vendor/github.com/h2non/go-is-svg/LICENSE сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,24 @@
The MIT License
Copyright (c) 2016 Tomas Aparicio
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

47
vendor/github.com/h2non/go-is-svg/README.md сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,47 @@
# go-is-svg [![Build Status](https://travis-ci.org/h2non/go-is-svg.png)](https://travis-ci.org/h2non/go-is-svg) [![GoDoc](https://godoc.org/github.com/h2non/go-is-svg?status.svg)](https://godoc.org/github.com/h2non/go-is-svg) [![Coverage Status](https://coveralls.io/repos/github/h2non/go-is-svg/badge.svg?branch=master)](https://coveralls.io/github/h2non/go-is-svg?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/h2non/go-is-svg)](https://goreportcard.com/report/github.com/h2non/go-is-svg)
Tiny package to verify if a given file buffer is an SVG image in Go (golang).
See also [filetype](https://github.com/h2non/filetype) package for binary files type inference.
## Installation
```bash
go get -u github.com/h2non/go-is-svg
```
## Example
```go
package main
import (
"fmt"
"io/ioutil"
svg "github.com/h2non/go-is-svg"
)
func main() {
buf, err := ioutil.ReadFile("_example/example.svg")
if err != nil {
fmt.Printf("Error: %s\n", err)
return
}
if svg.Is(buf) {
fmt.Println("File is an SVG")
} else {
fmt.Println("File is NOT an SVG")
}
}
```
Run example:
```bash
go run _example/example.go
```
## License
MIT - Tomas Aparicio

36
vendor/github.com/h2non/go-is-svg/svg.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,36 @@
package issvg
import (
"regexp"
"unicode/utf8"
)
var (
htmlCommentRegex = regexp.MustCompile("(?i)<!--([\\s\\S]*?)-->")
svgRegex = regexp.MustCompile(`(?i)^\s*(?:<\?xml[^>]*>\s*)?(?:<!doctype svg[^>]*>\s*)?<svg[^>]*>[^*]*<\/svg>\s*$`)
)
// isBinary checks if the given buffer is a binary file.
func isBinary(buf []byte) bool {
if len(buf) < 24 {
return false
}
for i := 0; i < 24; i++ {
charCode, _ := utf8.DecodeRuneInString(string(buf[i]))
if charCode == 65533 || charCode <= 8 {
return true
}
}
return false
}
// Is returns true if the given buffer is a valid SVG image.
func Is(buf []byte) bool {
return !isBinary(buf) && svgRegex.Match(htmlCommentRegex.ReplaceAll(buf, []byte{}))
}
// IsSVG returns true if the given buffer is a valid SVG image.
// Alias to: Is()
func IsSVG(buf []byte) bool {
return Is(buf)
}

2
vendor/modules.txt поставляемый
Просмотреть файл

@@ -52,6 +52,8 @@ github.com/gorilla/schema
github.com/gorilla/websocket
# github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
github.com/gregjones/httpcache
# github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c
github.com/h2non/go-is-svg
# github.com/hako/durafmt v0.0.0-20190612201238-650ed9f29a84
github.com/hako/durafmt
# github.com/hashicorp/errwrap v1.0.0