diff --git a/api4/plugin.go b/api4/plugin.go index 0c7f7a773b..13bed46757 100644 --- a/api4/plugin.go +++ b/api4/plugin.go @@ -151,6 +151,10 @@ func installMarketplacePlugin(c *Context, w http.ResponseWriter, r *http.Request } auditRec.AddMeta("plugin_id", pluginRequest.Id) + // Always install the latest compatible version + // https://mattermost.atlassian.net/browse/MM-41981 + pluginRequest.Version = "" + manifest, appErr := c.App.Channels().InstallMarketplacePlugin(pluginRequest) if appErr != nil { c.Err = appErr diff --git a/api4/plugin_test.go b/api4/plugin_test.go index 0fcfc6ddf7..5381be4704 100644 --- a/api4/plugin_test.go +++ b/api4/plugin_test.go @@ -1308,7 +1308,7 @@ func TestInstallMarketplacePlugin(t *testing.T) { }, } - request := &model.InstallMarketplacePluginRequest{Id: "", Version: ""} + request := &model.InstallMarketplacePluginRequest{Id: ""} th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { th.App.UpdateConfig(func(cfg *model.Config) { @@ -1374,7 +1374,7 @@ func TestInstallMarketplacePlugin(t *testing.T) { *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.MarketplaceURL = testServer.URL }) - pRequest := &model.InstallMarketplacePluginRequest{Id: "some_plugin_id", Version: "0.0.1"} + pRequest := &model.InstallMarketplacePluginRequest{Id: "some_plugin_id"} plugin, resp, err := client.InstallMarketplacePlugin(pRequest) require.Error(t, err) CheckInternalErrorStatus(t, resp) @@ -1395,7 +1395,7 @@ func TestInstallMarketplacePlugin(t *testing.T) { *cfg.PluginSettings.MarketplaceURL = testServer.URL *cfg.PluginSettings.AllowInsecureDownloadURL = true }) - pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin2", Version: "1.2.2"} + pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin2"} plugin, resp, err := client.InstallMarketplacePlugin(pRequest) require.Error(t, err) CheckInternalErrorStatus(t, resp) @@ -1425,7 +1425,7 @@ func TestInstallMarketplacePlugin(t *testing.T) { appErr := th.App.AddPublicKey("pub_key", key) require.Nil(t, appErr) - pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin2", Version: "1.2.3"} + pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin2"} manifest, _, err := client.InstallMarketplacePlugin(pRequest) require.NoError(t, err) require.NotNil(t, manifest) @@ -1447,6 +1447,51 @@ func TestInstallMarketplacePlugin(t *testing.T) { require.Nil(t, appErr) }, "verify, install and remove plugin") + th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { + 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 = true + *cfg.PluginSettings.MarketplaceURL = testServer.URL + }) + + 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) + + pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin2", Version: "9.9.9"} + manifest, _, err := client.InstallMarketplacePlugin(pRequest) + require.NoError(t, err) + require.NotNil(t, manifest) + require.Equal(t, "testplugin2", manifest.Id) + require.Equal(t, "1.2.3", manifest.Version) + + filePath := filepath.Join("plugins", "testplugin2.tar.gz.sig") + savedSigFile, appErr := th.App.ReadFile(filePath) + require.Nil(t, appErr) + require.EqualValues(t, sigFile, savedSigFile) + + _, err = client.RemovePlugin(manifest.Id) + require.NoError(t, err) + exists, appErr := th.App.FileExists(filePath) + require.Nil(t, appErr) + require.False(t, exists) + + appErr = th.App.DeletePublicKey("pub_key") + require.Nil(t, appErr) + }, "ignore version in Marketplace request") + th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { requestHandled := false @@ -1612,7 +1657,7 @@ func TestInstallMarketplacePlugin(t *testing.T) { require.Len(t, pluginsResp.Inactive, 0) // Should fail to install unknown prepackaged plugin - pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin", Version: "0.0.2"} + pRequest := &model.InstallMarketplacePluginRequest{Id: "testpluginXX"} manifest, resp, err := client.InstallMarketplacePlugin(pRequest) require.Error(t, err) CheckInternalErrorStatus(t, resp) @@ -1628,7 +1673,7 @@ func TestInstallMarketplacePlugin(t *testing.T) { require.Len(t, pluginsResp.Active, 0) require.Len(t, pluginsResp.Inactive, 0) - pRequest = &model.InstallMarketplacePluginRequest{Id: "testplugin", Version: "0.0.1"} + pRequest = &model.InstallMarketplacePluginRequest{Id: "testplugin"} manifest1, _, err := client.InstallMarketplacePlugin(pRequest) require.NoError(t, err) require.NotNil(t, manifest1) @@ -1643,7 +1688,7 @@ func TestInstallMarketplacePlugin(t *testing.T) { }}) // Try to install remote marketplace plugin - pRequest = &model.InstallMarketplacePluginRequest{Id: "testplugin2", Version: "1.2.3"} + pRequest = &model.InstallMarketplacePluginRequest{Id: "testplugin2"} manifest, resp, err = client.InstallMarketplacePlugin(pRequest) require.Error(t, err) CheckInternalErrorStatus(t, resp) @@ -1657,7 +1702,7 @@ func TestInstallMarketplacePlugin(t *testing.T) { *cfg.PluginSettings.AllowInsecureDownloadURL = true }) - pRequest = &model.InstallMarketplacePluginRequest{Id: "testplugin2", Version: "1.2.3"} + pRequest = &model.InstallMarketplacePluginRequest{Id: "testplugin2"} manifest2, _, err := client.InstallMarketplacePlugin(pRequest) require.NoError(t, err) require.NotNil(t, manifest2) @@ -1746,7 +1791,7 @@ func TestInstallMarketplacePlugin(t *testing.T) { require.Len(t, pluginsResp.Active, 0) require.Len(t, pluginsResp.Inactive, 0) - pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin", Version: "0.0.1"} + pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin"} manifest, resp, err := client.InstallMarketplacePlugin(pRequest) require.Error(t, err) CheckInternalErrorStatus(t, resp) @@ -1757,7 +1802,7 @@ func TestInstallMarketplacePlugin(t *testing.T) { require.Len(t, pluginsResp.Active, 0) require.Len(t, pluginsResp.Inactive, 0) - pRequest = &model.InstallMarketplacePluginRequest{Id: "testplugin2", Version: "1.2.3"} + pRequest = &model.InstallMarketplacePluginRequest{Id: "testplugin2"} manifest, resp, err = client.InstallMarketplacePlugin(pRequest) require.Error(t, err) CheckInternalErrorStatus(t, resp) diff --git a/app/plugin_install.go b/app/plugin_install.go index 6bda8e7848..35e4154654 100644 --- a/app/plugin_install.go +++ b/app/plugin_install.go @@ -196,23 +196,39 @@ func (ch *Channels) InstallMarketplacePlugin(request *model.InstallMarketplacePl signatureFile = bytes.NewReader(prepackagedPlugin.Signature) } - if *ch.cfgSvc.Config().PluginSettings.EnableRemoteMarketplace && pluginFile == nil { + if *ch.cfgSvc.Config().PluginSettings.EnableRemoteMarketplace { var plugin *model.BaseMarketplacePlugin plugin, appErr = ch.getRemoteMarketplacePlugin(request.Id, request.Version) if appErr != nil { return nil, appErr } - downloadedPluginBytes, err := ch.srv.downloadFromURL(plugin.DownloadURL) - if err != nil { - return nil, model.NewAppError("InstallMarketplacePlugin", "app.plugin.install_marketplace_plugin.app_error", nil, err.Error(), http.StatusInternalServerError) + var prepackagedVersion semver.Version + if prepackagedPlugin != nil { + var err error + prepackagedVersion, err = semver.Parse(prepackagedPlugin.Manifest.Version) + if err != nil { + return nil, model.NewAppError("InstallMarketplacePlugin", "app.plugin.invalid_version.app_error", nil, err.Error(), http.StatusBadRequest) + } } - signature, err := plugin.DecodeSignature() + + marketplaceVersion, err := semver.Parse(plugin.Manifest.Version) if err != nil { - return nil, model.NewAppError("InstallMarketplacePlugin", "app.plugin.signature_decode.app_error", nil, err.Error(), http.StatusNotImplemented) + return nil, model.NewAppError("InstallMarketplacePlugin", "app.prepackged-plugin.invalid_version.app_error", nil, err.Error(), http.StatusBadRequest) + } + + if prepackagedVersion.LT(marketplaceVersion) { // Always true if no prepackaged plugin was found + downloadedPluginBytes, err := ch.srv.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 } - pluginFile = bytes.NewReader(downloadedPluginBytes) - signatureFile = signature } if pluginFile == nil { diff --git a/i18n/en.json b/i18n/en.json index e5ca71e033..b367792d7a 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -5999,6 +5999,10 @@ "id": "app.preference.save.updating.app_error", "translation": "We encountered an error while updating preferences." }, + { + "id": "app.prepackged-plugin.invalid_version.app_error", + "translation": "Prepackged plugin version could not be parsed." + }, { "id": "app.reaction.bulk_get_for_post_ids.app_error", "translation": "Unable to get reactions for post."