// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. package api4 import ( "bytes" "context" "encoding/base64" "encoding/json" "fmt" "io" "net/http" "net/http/httptest" "os" "path/filepath" "sort" "strings" "testing" "time" svg "github.com/h2non/go-is-svg" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/plugin" "github.com/mattermost/mattermost/server/public/plugin/utils" "github.com/mattermost/mattermost/server/v8" "github.com/mattermost/mattermost/server/v8/channels/testlib" "github.com/mattermost/mattermost/server/v8/channels/utils/fileutils" ) func TestPlugin(t *testing.T) { mainHelper.Parallel(t) th := Setup(t) defer th.TearDown() th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { statesJson, err := json.Marshal(th.App.Config().PluginSettings.PluginStates) require.NoError(t, err) states := map[string]*model.PluginState{} err = json.Unmarshal(statesJson, &states) require.NoError(t, err) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true *cfg.PluginSettings.EnableUploads = true *cfg.PluginSettings.AllowInsecureDownloadURL = true }) path, _ := fileutils.FindDir("tests") tarData, err := os.ReadFile(filepath.Join(path, "testplugin.tar.gz")) require.NoError(t, err) // Install from URL testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(http.StatusOK) _, err = res.Write(tarData) require.NoError(t, err) })) defer func() { testServer.Close() }() url := testServer.URL manifest, _, err := client.InstallPluginFromURL(context.Background(), url, false) require.NoError(t, err) assert.Equal(t, "testplugin", manifest.Id) _, resp, err := client.InstallPluginFromURL(context.Background(), url, false) require.Error(t, err) CheckBadRequestStatus(t, resp) manifest, _, err = client.InstallPluginFromURL(context.Background(), url, true) require.NoError(t, err) assert.Equal(t, "testplugin", manifest.Id) // Stored in File Store: Install Plugin from URL case pluginStored, appErr := th.App.FileExists("./plugins/" + manifest.Id + ".tar.gz") assert.Nil(t, appErr) assert.True(t, pluginStored) _, err = client.RemovePlugin(context.Background(), manifest.Id) require.NoError(t, err) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false }) _, resp, err = client.InstallPluginFromURL(context.Background(), url, false) require.Error(t, err) CheckNotImplementedStatus(t, resp) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true }) _, resp, err = th.Client.InstallPluginFromURL(context.Background(), url, false) require.Error(t, err) CheckForbiddenStatus(t, resp) _, resp, err = client.InstallPluginFromURL(context.Background(), "http://nodata", false) require.Error(t, err) CheckBadRequestStatus(t, resp) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.AllowInsecureDownloadURL = false }) _, resp, err = client.InstallPluginFromURL(context.Background(), url, false) require.Error(t, err) CheckBadRequestStatus(t, resp) // Successful upload manifest, _, err = client.UploadPlugin(context.Background(), bytes.NewReader(tarData)) require.NoError(t, err) assert.Equal(t, "testplugin", manifest.Id) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableUploads = true }) manifest, _, err = client.UploadPluginForced(context.Background(), bytes.NewReader(tarData)) defer os.RemoveAll("plugins/testplugin") require.NoError(t, err) assert.Equal(t, "testplugin", manifest.Id) // Stored in File Store: Upload Plugin case pluginStored, appErr = th.App.FileExists("./plugins/" + manifest.Id + ".tar.gz") assert.Nil(t, appErr) assert.True(t, pluginStored) // Upload error cases _, resp, err = client.UploadPlugin(context.Background(), bytes.NewReader([]byte("badfile"))) require.Error(t, err) CheckBadRequestStatus(t, resp) plugin_sz := int64(111 * 1024 * 1024) fd, err := os.Create(filepath.Join(path, "big_testplugin.tar.gz")) require.NoError(t, err) _, err = fd.Seek(plugin_sz-1, 0) require.NoError(t, err) _, err = fd.Write([]byte{0}) require.NoError(t, err) err = fd.Close() require.NoError(t, err) bigData, err := os.ReadFile(filepath.Join(path, "big_testplugin.tar.gz")) require.NoError(t, err) _, resp, err = client.UploadPlugin(context.Background(), bytes.NewReader(bigData)) require.Error(t, err) CheckRequestEntityTooLargeStatus(t, resp) err = os.Remove(filepath.Join(path, "big_testplugin.tar.gz")) require.NoError(t, err) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false }) _, resp, err = client.UploadPlugin(context.Background(), bytes.NewReader(tarData)) require.Error(t, err) CheckNotImplementedStatus(t, resp) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true *cfg.PluginSettings.EnableUploads = false }) _, resp, err = client.UploadPlugin(context.Background(), bytes.NewReader(tarData)) require.Error(t, err) CheckNotImplementedStatus(t, resp) _, resp, err = client.InstallPluginFromURL(context.Background(), url, false) require.Error(t, err) CheckNotImplementedStatus(t, resp) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableUploads = true }) _, resp, err = th.Client.UploadPlugin(context.Background(), bytes.NewReader(tarData)) require.Error(t, err) CheckForbiddenStatus(t, resp) // Successful gets pluginsResp, _, err := client.GetPlugins(context.Background()) require.NoError(t, err) found := false for _, m := range pluginsResp.Inactive { if m.Id == manifest.Id { found = true } } assert.True(t, found) found = false for _, m := range pluginsResp.Active { if m.Id == manifest.Id { found = true } } assert.False(t, found) // Successful activate _, err = client.EnablePlugin(context.Background(), manifest.Id) require.NoError(t, err) pluginsResp, _, err = client.GetPlugins(context.Background()) require.NoError(t, err) found = false for _, m := range pluginsResp.Active { if m.Id == manifest.Id { found = true } } assert.True(t, found) // Activate error case resp, err = client.EnablePlugin(context.Background(), "junk") require.Error(t, err) CheckNotFoundStatus(t, resp) resp, err = client.EnablePlugin(context.Background(), "JUNK") require.Error(t, err) CheckNotFoundStatus(t, resp) // Successful deactivate _, err = client.DisablePlugin(context.Background(), manifest.Id) require.NoError(t, err) pluginsResp, _, err = client.GetPlugins(context.Background()) require.NoError(t, err) found = false for _, m := range pluginsResp.Inactive { if m.Id == manifest.Id { found = true } } assert.True(t, found) // Deactivate error case resp, err = client.DisablePlugin(context.Background(), "junk") require.Error(t, err) CheckNotFoundStatus(t, resp) // Get error cases th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false }) _, resp, err = client.GetPlugins(context.Background()) require.Error(t, err) CheckNotImplementedStatus(t, resp) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true }) _, resp, err = th.Client.GetPlugins(context.Background()) require.Error(t, err) CheckForbiddenStatus(t, resp) // Successful webapp get _, err = client.EnablePlugin(context.Background(), manifest.Id) require.NoError(t, err) manifests, _, err := th.Client.GetWebappPlugins(context.Background()) require.NoError(t, err) found = false for _, m := range manifests { if m.Id == manifest.Id { found = true } } assert.True(t, found) // Successful remove _, err = client.RemovePlugin(context.Background(), manifest.Id) require.NoError(t, err) // Remove error cases resp, err = client.RemovePlugin(context.Background(), manifest.Id) require.Error(t, err) CheckNotFoundStatus(t, resp) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false }) resp, err = client.RemovePlugin(context.Background(), manifest.Id) require.Error(t, err) CheckNotImplementedStatus(t, resp) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true }) resp, err = th.Client.RemovePlugin(context.Background(), manifest.Id) require.Error(t, err) CheckForbiddenStatus(t, resp) resp, err = client.RemovePlugin(context.Background(), "bad.id") require.Error(t, err) CheckNotFoundStatus(t, resp) }) } func TestNotifyClusterPluginEvent(t *testing.T) { mainHelper.Parallel(t) th := Setup(t) defer th.TearDown() testCluster := &testlib.FakeClusterInterface{} th.Server.Platform().SetCluster(testCluster) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true *cfg.PluginSettings.EnableUploads = true }) path, _ := fileutils.FindDir("tests") tarData, err := os.ReadFile(filepath.Join(path, "testplugin.tar.gz")) require.NoError(t, err) testCluster.ClearMessages() // Successful upload manifest, _, err := th.SystemAdminClient.UploadPlugin(context.Background(), bytes.NewReader(tarData)) require.NoError(t, err) require.Equal(t, "testplugin", manifest.Id) // Stored in File Store: Upload Plugin case expectedPath := filepath.Join("./plugins", manifest.Id) + ".tar.gz" pluginStored, appErr := th.App.FileExists(expectedPath) require.Nil(t, appErr) require.True(t, pluginStored) messages := testCluster.GetMessages() expectedPluginData := model.PluginEventData{ Id: manifest.Id, } buf, _ := json.Marshal(expectedPluginData) expectedInstallMessage := &model.ClusterMessage{ Event: model.ClusterEventInstallPlugin, SendType: model.ClusterSendReliable, WaitForAllToSend: true, Data: buf, } actualMessages := findClusterMessages(model.ClusterEventInstallPlugin, messages) require.Equal(t, []*model.ClusterMessage{expectedInstallMessage}, actualMessages) // Upgrade testCluster.ClearMessages() manifest, _, err = th.SystemAdminClient.UploadPluginForced(context.Background(), bytes.NewReader(tarData)) require.NoError(t, err) require.Equal(t, "testplugin", manifest.Id) // Successful remove webSocketClient := th.CreateConnectedWebSocketClientWithClient(t, th.SystemAdminClient) done := make(chan bool) go func() { for { select { case resp := <-webSocketClient.EventChannel: if resp.EventType() == model.WebsocketEventPluginStatusesChanged && len(resp.GetData()["plugin_statuses"].([]any)) == 0 { done <- true return } case <-time.After(5 * time.Second): done <- false return } } }() testCluster.ClearMessages() _, err = th.SystemAdminClient.RemovePlugin(context.Background(), manifest.Id) require.NoError(t, err) result := <-done require.True(t, result, "plugin_statuses_changed websocket event was not received") messages = testCluster.GetMessages() expectedRemoveMessage := &model.ClusterMessage{ Event: model.ClusterEventRemovePlugin, SendType: model.ClusterSendReliable, WaitForAllToSend: true, Data: buf, } actualMessages = findClusterMessages(model.ClusterEventRemovePlugin, messages) require.Equal(t, []*model.ClusterMessage{expectedRemoveMessage}, actualMessages) pluginStored, appErr = th.App.FileExists(expectedPath) require.Nil(t, appErr) require.False(t, pluginStored) } func TestDisableOnRemove(t *testing.T) { mainHelper.Parallel(t) path, _ := fileutils.FindDir("tests") tarData, err := os.ReadFile(filepath.Join(path, "testplugin.tar.gz")) require.NoError(t, err) testCases := []struct { Description string Upgrade bool }{ { "Remove without upgrading", false, }, { "Remove after upgrading", true, }, } th := Setup(t).InitBasic() defer th.TearDown() for _, tc := range testCases { t.Run(tc.Description, func(t *testing.T) { th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true *cfg.PluginSettings.EnableUploads = true }) // Upload manifest, _, err := client.UploadPlugin(context.Background(), bytes.NewReader(tarData)) require.NoError(t, err) require.Equal(t, "testplugin", manifest.Id) // Check initial status pluginsResp, _, err := client.GetPlugins(context.Background()) require.NoError(t, err) require.Empty(t, pluginsResp.Active) require.Equal(t, pluginsResp.Inactive, []*model.PluginInfo{{ Manifest: *manifest, }}) // Enable plugin _, err = client.EnablePlugin(context.Background(), manifest.Id) require.NoError(t, err) // Confirm enabled status pluginsResp, _, err = client.GetPlugins(context.Background()) require.NoError(t, err) require.Empty(t, pluginsResp.Inactive) require.Equal(t, pluginsResp.Active, []*model.PluginInfo{{ Manifest: *manifest, }}) if tc.Upgrade { // Upgrade manifest, _, err = client.UploadPluginForced(context.Background(), bytes.NewReader(tarData)) require.NoError(t, err) require.Equal(t, "testplugin", manifest.Id) // Plugin should remain active pluginsResp, _, err = client.GetPlugins(context.Background()) require.NoError(t, err) require.Empty(t, pluginsResp.Inactive) require.Equal(t, pluginsResp.Active, []*model.PluginInfo{{ Manifest: *manifest, }}) } // Remove plugin _, err = client.RemovePlugin(context.Background(), manifest.Id) require.NoError(t, err) // Plugin should have no status pluginsResp, _, err = client.GetPlugins(context.Background()) require.NoError(t, err) require.Empty(t, pluginsResp.Inactive) require.Empty(t, pluginsResp.Active) // Upload same plugin manifest, _, err = client.UploadPlugin(context.Background(), bytes.NewReader(tarData)) require.NoError(t, err) require.Equal(t, "testplugin", manifest.Id) // Plugin should be inactive pluginsResp, _, err = client.GetPlugins(context.Background()) require.NoError(t, err) require.Empty(t, pluginsResp.Active) require.Equal(t, pluginsResp.Inactive, []*model.PluginInfo{{ Manifest: *manifest, }}) // Clean up _, err = client.RemovePlugin(context.Background(), manifest.Id) require.NoError(t, err) }) }) } } func TestGetMarketplacePlugins(t *testing.T) { th := Setup(t) defer th.TearDown() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true *cfg.PluginSettings.EnableUploads = true *cfg.PluginSettings.EnableMarketplace = false }) th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = false *cfg.PluginSettings.MarketplaceURL = "invalid.com" }) plugins, resp, err := client.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.Error(t, err) CheckNotImplementedStatus(t, resp) require.Nil(t, plugins) }, "marketplace disabled") th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.MarketplaceURL = "invalid.com" }) plugins, resp, err := client.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.Error(t, err) CheckInternalErrorStatus(t, resp) require.Nil(t, plugins) }, "no server") t.Run("no permission", func(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.MarketplaceURL = "invalid.com" }) plugins, resp, err := th.Client.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.Error(t, err) CheckForbiddenStatus(t, resp) require.Nil(t, plugins) }) th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(http.StatusOK) json, err := json.Marshal([]*model.MarketplacePlugin{}) require.NoError(t, err) _, err = res.Write(json) require.NoError(t, err) })) defer func() { testServer.Close() }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.MarketplaceURL = testServer.URL }) plugins, _, err := client.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) require.Empty(t, plugins) }, "empty response from server") th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { serverVersion, ok := req.URL.Query()["server_version"] require.True(t, ok) require.Len(t, serverVersion, 1) require.Equal(t, model.CurrentVersion, serverVersion[0]) require.NotEqual(t, 0, len(serverVersion[0])) res.WriteHeader(http.StatusOK) json, err := json.Marshal([]*model.MarketplacePlugin{}) require.NoError(t, err) _, err = res.Write(json) require.NoError(t, err) })) defer func() { testServer.Close() }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.MarketplaceURL = testServer.URL }) plugins, _, err := client.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) require.Empty(t, plugins) }, "verify server version is passed through") th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { licenseType, ok := req.URL.Query()["enterprise_plugins"] require.True(t, ok) require.Len(t, licenseType, 1) require.Equal(t, "false", licenseType[0]) res.WriteHeader(http.StatusOK) json, err := json.Marshal([]*model.MarketplacePlugin{}) require.NoError(t, err) _, err = res.Write(json) require.NoError(t, err) })) defer func() { testServer.Close() }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.MarketplaceURL = testServer.URL }) plugins, _, err := client.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) require.Empty(t, plugins) }, "verify EnterprisePlugins is false for TE") th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { licenseType, ok := req.URL.Query()["enterprise_plugins"] require.True(t, ok) require.Len(t, licenseType, 1) require.Equal(t, "false", licenseType[0]) res.WriteHeader(http.StatusOK) json, err := json.Marshal([]*model.MarketplacePlugin{}) require.NoError(t, err) _, err = res.Write(json) require.NoError(t, err) })) defer func() { testServer.Close() }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.MarketplaceURL = testServer.URL }) l := model.NewTestLicense() // model.NewTestLicense generates a E20 license *l.Features.EnterprisePlugins = false th.App.Srv().SetLicense(l) plugins, _, err := client.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) require.Empty(t, plugins) }, "verify EnterprisePlugins is false for E10") th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { licenseType, ok := req.URL.Query()["enterprise_plugins"] require.True(t, ok) require.Len(t, licenseType, 1) require.Equal(t, "true", licenseType[0]) res.WriteHeader(http.StatusOK) json, err := json.Marshal([]*model.MarketplacePlugin{}) require.NoError(t, err) _, err = res.Write(json) require.NoError(t, err) })) defer func() { testServer.Close() }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.MarketplaceURL = testServer.URL }) th.App.Srv().SetLicense(model.NewTestLicense("enterprise_plugins")) plugins, _, err := client.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) require.Empty(t, plugins) }, "verify EnterprisePlugins is true for E20") th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { cloud, ok := req.URL.Query()["cloud"] require.True(t, ok) require.Len(t, cloud, 1) require.Equal(t, "false", cloud[0]) res.WriteHeader(http.StatusOK) json, err := json.Marshal([]*model.MarketplacePlugin{}) require.NoError(t, err) _, err = res.Write(json) require.NoError(t, err) })) defer func() { testServer.Close() }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.MarketplaceURL = testServer.URL }) plugins, _, err := client.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) require.Empty(t, plugins) }, "verify EnterprisePlugins is false if there is no license") th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { cloud, ok := req.URL.Query()["cloud"] require.True(t, ok) require.Len(t, cloud, 1) require.Equal(t, "true", cloud[0]) res.WriteHeader(http.StatusOK) json, err := json.Marshal([]*model.MarketplacePlugin{}) require.NoError(t, err) _, err = res.Write(json) require.NoError(t, err) })) defer func() { testServer.Close() }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.MarketplaceURL = testServer.URL }) th.App.Srv().SetLicense(model.NewTestLicense("cloud")) plugins, _, err := client.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) require.Empty(t, plugins) }, "verify Cloud is true for cloud license") } func TestGetInstalledMarketplacePlugins(t *testing.T) { samplePlugins := []*model.MarketplacePlugin{ { BaseMarketplacePlugin: &model.BaseMarketplacePlugin{ HomepageURL: "https://example.com/mattermost/mattermost-plugin-nps", IconData: "https://example.com/icon.svg", DownloadURL: "https://example.com/mattermost/mattermost-plugin-nps/releases/download/v1.0.4/com.mattermost.nps-1.0.4.tar.gz", Labels: []model.MarketplaceLabel{ { Name: "someName", Description: "some Description", }, }, Manifest: &model.Manifest{ Id: "com.mattermost.nps", Name: "User Satisfaction Surveys", Description: "This plugin sends quarterly user satisfaction surveys to gather feedback and help improve Mattermost.", Version: "1.0.4", MinServerVersion: "5.14.0", }, }, InstalledVersion: "", }, } path, _ := fileutils.FindDir("tests") tarData, err := os.ReadFile(filepath.Join(path, "testplugin.tar.gz")) require.NoError(t, err) t.Run("marketplace client returns not-installed plugin", func(t *testing.T) { th := Setup(t) defer th.TearDown() testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(http.StatusOK) json, err := json.Marshal(samplePlugins) require.NoError(t, err) _, err = res.Write(json) require.NoError(t, err) })) defer func() { testServer.Close() }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true *cfg.PluginSettings.EnableUploads = true *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.MarketplaceURL = testServer.URL }) plugins, _, err := th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) require.Equal(t, samplePlugins, plugins) manifest, _, err := th.SystemAdminClient.UploadPlugin(context.Background(), bytes.NewReader(tarData)) require.NoError(t, err) testIcon, err := os.ReadFile(filepath.Join(path, "test.svg")) require.NoError(t, err) require.True(t, svg.Is(testIcon)) testIconData := fmt.Sprintf("data:image/svg+xml;base64,%s", base64.StdEncoding.EncodeToString(testIcon)) expectedPlugins := append(samplePlugins, &model.MarketplacePlugin{ BaseMarketplacePlugin: &model.BaseMarketplacePlugin{ HomepageURL: "https://example.com/homepage", IconData: testIconData, DownloadURL: "", ReleaseNotesURL: "https://example.com/releases/v0.0.1", Labels: []model.MarketplaceLabel{{ Name: "Local", Description: "This plugin is not listed in the marketplace", }}, Manifest: manifest, }, InstalledVersion: manifest.Version, }) sort.SliceStable(expectedPlugins, func(i, j int) bool { return strings.ToLower(expectedPlugins[i].Manifest.Name) < strings.ToLower(expectedPlugins[j].Manifest.Name) }) plugins, _, err = th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) require.Equal(t, expectedPlugins, plugins) _, err = th.SystemAdminClient.RemovePlugin(context.Background(), manifest.Id) require.NoError(t, err) plugins, _, err = th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) require.Equal(t, samplePlugins, plugins) }) t.Run("marketplace client returns installed plugin", func(t *testing.T) { th := Setup(t) defer th.TearDown() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true *cfg.PluginSettings.EnableUploads = true *cfg.PluginSettings.EnableMarketplace = true }) manifest, _, err := th.SystemAdminClient.UploadPlugin(context.Background(), bytes.NewReader(tarData)) require.NoError(t, err) newPlugin := &model.MarketplacePlugin{ BaseMarketplacePlugin: &model.BaseMarketplacePlugin{ HomepageURL: "HomepageURL", IconData: "IconData", DownloadURL: "DownloadURL", Manifest: manifest, }, InstalledVersion: manifest.Version, } expectedPlugins := append(samplePlugins, newPlugin) sort.SliceStable(expectedPlugins, func(i, j int) bool { return strings.ToLower(expectedPlugins[i].Manifest.Name) < strings.ToLower(expectedPlugins[j].Manifest.Name) }) testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(http.StatusOK) var out []byte out, err = json.Marshal([]*model.MarketplacePlugin{samplePlugins[0], newPlugin}) require.NoError(t, err) _, err = res.Write(out) require.NoError(t, err) })) defer func() { testServer.Close() }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.MarketplaceURL = testServer.URL }) plugins, _, err := th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) require.Equal(t, expectedPlugins, plugins) _, err = th.SystemAdminClient.RemovePlugin(context.Background(), manifest.Id) require.NoError(t, err) plugins, _, err = th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) newPlugin.InstalledVersion = "" require.Equal(t, expectedPlugins, plugins) }) } func TestSearchGetMarketplacePlugins(t *testing.T) { samplePlugins := []*model.MarketplacePlugin{ { BaseMarketplacePlugin: &model.BaseMarketplacePlugin{ HomepageURL: "example.com/mattermost/mattermost-plugin-nps", IconData: "Cjxzdmcgdmlld0JveD0nMCAwIDEwNSA5MycgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJz4KPHBhdGggZD0nTTY2LDBoMzl2OTN6TTM4LDBoLTM4djkzek01MiwzNWwyNSw1OGgtMTZsLTgtMThoLTE4eicgZmlsbD0nI0VEMUMyNCcvPgo8L3N2Zz4K", DownloadURL: "example.com/mattermost/mattermost-plugin-nps/releases/download/v1.0.4/com.mattermost.nps-1.0.4.tar.gz", Manifest: &model.Manifest{ Id: "com.mattermost.nps", Name: "User Satisfaction Surveys", Description: "This plugin sends quarterly user satisfaction surveys to gather feedback and help improve Mattermost.", Version: "1.0.4", MinServerVersion: "5.14.0", }, }, InstalledVersion: "", }, } path, _ := fileutils.FindDir("tests") tarData, err := os.ReadFile(filepath.Join(path, "testplugin.tar.gz")) require.NoError(t, err) tarDataV2, err := os.ReadFile(filepath.Join(path, "testplugin2.tar.gz")) require.NoError(t, err) testIcon, err := os.ReadFile(filepath.Join(path, "test.svg")) require.NoError(t, err) require.True(t, svg.Is(testIcon)) testIconData := fmt.Sprintf("data:image/svg+xml;base64,%s", base64.StdEncoding.EncodeToString(testIcon)) t.Run("search installed plugin", func(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(http.StatusOK) json, err := json.Marshal(samplePlugins) require.NoError(t, err) _, err = res.Write(json) require.NoError(t, err) })) defer func() { testServer.Close() }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true *cfg.PluginSettings.EnableUploads = true *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.MarketplaceURL = testServer.URL }) plugins, _, err := th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) require.Equal(t, samplePlugins, plugins) manifest, _, err := th.SystemAdminClient.UploadPlugin(context.Background(), bytes.NewReader(tarData)) require.NoError(t, err) plugin1 := &model.MarketplacePlugin{ BaseMarketplacePlugin: &model.BaseMarketplacePlugin{ HomepageURL: "https://example.com/homepage", IconData: testIconData, DownloadURL: "", ReleaseNotesURL: "https://example.com/releases/v0.0.1", Labels: []model.MarketplaceLabel{{ Name: "Local", Description: "This plugin is not listed in the marketplace", }}, Manifest: manifest, }, InstalledVersion: manifest.Version, } expectedPlugins := append(samplePlugins, plugin1) manifest, _, err = th.SystemAdminClient.UploadPlugin(context.Background(), bytes.NewReader(tarDataV2)) require.NoError(t, err) plugin2 := &model.MarketplacePlugin{ BaseMarketplacePlugin: &model.BaseMarketplacePlugin{ IconData: testIconData, HomepageURL: "https://example.com/homepage", DownloadURL: "", ReleaseNotesURL: "https://example.com/releases/v1.2.3", Labels: []model.MarketplaceLabel{{ Name: "Local", Description: "This plugin is not listed in the marketplace", }}, Manifest: manifest, }, InstalledVersion: manifest.Version, } 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) }) plugins, _, err = th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) require.Equal(t, expectedPlugins, plugins) // Search for plugins from the server plugins, _, err = th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{Filter: "testplugin2"}) require.NoError(t, err) require.Equal(t, []*model.MarketplacePlugin{plugin2}, plugins) plugins, _, err = th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{Filter: "a second plugin"}) require.NoError(t, err) require.Equal(t, []*model.MarketplacePlugin{plugin2}, plugins) plugins, _, err = th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{Filter: "User Satisfaction Surveys"}) require.NoError(t, err) require.Equal(t, samplePlugins, plugins) plugins, _, err = th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{Filter: "NOFILTER"}) require.NoError(t, err) require.Nil(t, plugins) // cleanup _, err = th.SystemAdminClient.RemovePlugin(context.Background(), plugin1.Manifest.Id) require.NoError(t, err) _, err = th.SystemAdminClient.RemovePlugin(context.Background(), plugin2.Manifest.Id) require.NoError(t, err) }) } func TestGetLocalPluginInMarketplace(t *testing.T) { th := Setup(t) 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) _, err = res.Write(json) require.NoError(t, err) })) 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, _, err := th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) 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 := os.ReadFile(filepath.Join(path, "testplugin.tar.gz")) require.NoError(t, err) manifest, _, err := th.SystemAdminClient.UploadPlugin(context.Background(), bytes.NewReader(tarData)) require.NoError(t, err) plugins, _, err := th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) require.Len(t, plugins, 2) _, err = th.SystemAdminClient.RemovePlugin(context.Background(), manifest.Id) require.NoError(t, err) }) 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, _, err := th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) require.Len(t, plugins, 0) // Upload one local plugin path, _ := fileutils.FindDir("tests") tarData, err := os.ReadFile(filepath.Join(path, "testplugin.tar.gz")) require.NoError(t, err) manifest, _, err := th.SystemAdminClient.UploadPlugin(context.Background(), bytes.NewReader(tarData)) require.NoError(t, err) testIcon, err := os.ReadFile(filepath.Join(path, "test.svg")) require.NoError(t, err) require.True(t, svg.Is(testIcon)) testIconData := fmt.Sprintf("data:image/svg+xml;base64,%s", base64.StdEncoding.EncodeToString(testIcon)) newPlugin := &model.MarketplacePlugin{ BaseMarketplacePlugin: &model.BaseMarketplacePlugin{ IconData: testIconData, HomepageURL: "https://example.com/homepage", ReleaseNotesURL: "https://example.com/releases/v0.0.1", Manifest: manifest, }, InstalledVersion: manifest.Version, } plugins, _, err = th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) // Only get the local plugins require.Len(t, plugins, 1) require.Equal(t, newPlugin, plugins[0]) _, err = th.SystemAdminClient.RemovePlugin(context.Background(), manifest.Id) require.NoError(t, err) }) 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 := os.ReadFile(filepath.Join(path, "testplugin.tar.gz")) require.NoError(t, err) manifest, _, err := th.SystemAdminClient.UploadPlugin(context.Background(), bytes.NewReader(tarData)) require.NoError(t, err) testIcon, err := os.ReadFile(filepath.Join(path, "test.svg")) require.NoError(t, err) require.True(t, svg.Is(testIcon)) testIconData := fmt.Sprintf("data:image/svg+xml;base64,%s", base64.StdEncoding.EncodeToString(testIcon)) newPlugin := &model.MarketplacePlugin{ BaseMarketplacePlugin: &model.BaseMarketplacePlugin{ Manifest: manifest, IconData: testIconData, HomepageURL: "https://example.com/homepage", ReleaseNotesURL: "https://example.com/releases/v0.0.1", Labels: []model.MarketplaceLabel{{ Name: "Local", Description: "This plugin is not listed in the marketplace", }}, }, InstalledVersion: manifest.Version, } plugins, _, err := th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{LocalOnly: true}) require.NoError(t, err) require.Len(t, plugins, 1) require.Equal(t, newPlugin, plugins[0]) _, err = th.SystemAdminClient.RemovePlugin(context.Background(), manifest.Id) require.NoError(t, err) }) } func TestGetRemotePluginInMarketplace(t *testing.T) { mainHelper.Parallel(t) th := Setup(t) 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) _, err = res.Write(json) require.NoError(t, err) })) defer testServer.Close() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.EnableRemoteMarketplace = true *cfg.PluginSettings.EnableUploads = true *cfg.PluginSettings.MarketplaceURL = testServer.URL }) // Upload one local plugin path, _ := fileutils.FindDir("tests") tarData, err := os.ReadFile(filepath.Join(path, "testplugin.tar.gz")) require.NoError(t, err) manifest, _, err := th.SystemAdminClient.UploadPlugin(context.Background(), bytes.NewReader(tarData)) require.NoError(t, err) plugins, _, err := th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{RemoteOnly: true}) require.NoError(t, err) require.Len(t, plugins, 1) require.Equal(t, samplePlugins[0], plugins[0]) _, err = th.SystemAdminClient.RemovePlugin(context.Background(), manifest.Id) require.NoError(t, err) } func TestGetPrepackagedPluginInMarketplace(t *testing.T) { th := Setup(t) 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) _, err = res.Write(json) require.NoError(t, err) })) 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}, nil) t.Run("prepackaged plugins are shown in Cloud", func(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableRemoteMarketplace = true *cfg.PluginSettings.EnableUploads = true }) lic := th.App.Srv().License() th.App.Srv().SetLicense(model.NewTestLicense("cloud")) defer th.App.Srv().SetLicense(lic) plugins, _, err := th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) 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("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, _, err := th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) 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, _, err := th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) // 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}, nil) plugins, _, err := th.SystemAdminClient.GetMarketplacePlugins(context.Background(), &model.MarketplacePluginFilter{}) require.NoError(t, err) require.Len(t, plugins, 1) require.Equal(t, newerPrepackagePlugin.Manifest, plugins[0].Manifest) }) } func TestInstallMarketplacePlugin(t *testing.T) { path, _ := fileutils.FindDir("tests") th := SetupConfig(t, func(cfg *model.Config) { *cfg.PluginSettings.Enable = true *cfg.PluginSettings.EnableUploads = true *cfg.PluginSettings.EnableMarketplace = false cfg.PluginSettings.SignaturePublicKeyFiles = []string{ filepath.Join(path, "development-private-key.asc"), } }).InitBasic() defer th.TearDown() signatureFilename := "testplugin2.tar.gz.sig" signatureFileReader, err := os.Open(filepath.Join(path, signatureFilename)) require.NoError(t, err) sigFile, err := io.ReadAll(signatureFileReader) require.NoError(t, err) pluginSignature := base64.StdEncoding.EncodeToString(sigFile) tarData, err := os.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) _, err = res.Write(tarData) require.NoError(t, err) })) defer pluginServer.Close() samplePlugins := []*model.MarketplacePlugin{ { BaseMarketplacePlugin: &model.BaseMarketplacePlugin{ HomepageURL: "https://example.com/mattermost/mattermost-plugin-nps", IconData: "https://example.com/icon.svg", DownloadURL: pluginServer.URL, Manifest: &model.Manifest{ Id: "testplugin2", Name: "testplugin2", Description: "a second plugin", Version: "1.2.2", MinServerVersion: "", }, }, InstalledVersion: "", }, { BaseMarketplacePlugin: &model.BaseMarketplacePlugin{ HomepageURL: "https://example.com/mattermost/mattermost-plugin-nps", IconData: "https://example.com/icon.svg", DownloadURL: pluginServer.URL, Manifest: &model.Manifest{ Id: "testplugin2", Name: "testplugin2", Description: "a second plugin", Version: "1.2.3", MinServerVersion: "", }, Signature: pluginSignature, }, InstalledVersion: "", }, } request := &model.InstallMarketplacePluginRequest{Id: ""} th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = false *cfg.PluginSettings.MarketplaceURL = "invalid.com" }) plugin, resp, err := client.InstallMarketplacePlugin(context.Background(), request) require.Error(t, err) CheckNotImplementedStatus(t, resp) require.Nil(t, plugin) }, "marketplace disabled") th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true *cfg.PluginSettings.RequirePluginSignature = true }) manifest, resp, err := client.UploadPlugin(context.Background(), bytes.NewReader(tarData)) require.Error(t, err) CheckNotImplementedStatus(t, resp) require.Nil(t, manifest) manifest, resp, err = client.InstallPluginFromURL(context.Background(), "some_url", true) require.Error(t, err) CheckNotImplementedStatus(t, resp) require.Nil(t, manifest) }, "RequirePluginSignature enabled") th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.MarketplaceURL = "invalid.com" }) plugin, resp, err := client.InstallMarketplacePlugin(context.Background(), request) require.Error(t, err) CheckInternalErrorStatus(t, resp) require.Nil(t, plugin) }, "no server") t.Run("no permission", func(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.MarketplaceURL = "invalid.com" }) plugin, resp, err := th.Client.InstallMarketplacePlugin(context.Background(), request) require.Error(t, err) CheckForbiddenStatus(t, resp) require.Nil(t, plugin) }) th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(http.StatusOK) json, err := json.Marshal([]*model.MarketplacePlugin{}) require.NoError(t, err) _, err = res.Write(json) require.NoError(t, err) })) defer testServer.Close() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.MarketplaceURL = testServer.URL }) pRequest := &model.InstallMarketplacePluginRequest{Id: "some_plugin_id"} plugin, resp, err := client.InstallMarketplacePlugin(context.Background(), pRequest) require.Error(t, err) CheckInternalErrorStatus(t, resp) require.Nil(t, plugin) }, "plugin not found on the server") th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { 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) _, err = res.Write(json) require.NoError(t, err) })) defer testServer.Close() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.MarketplaceURL = testServer.URL *cfg.PluginSettings.AllowInsecureDownloadURL = true }) pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin2"} plugin, resp, err := client.InstallMarketplacePlugin(context.Background(), pRequest) require.Error(t, err) CheckInternalErrorStatus(t, resp) require.Nil(t, plugin) }, "plugin not verified") 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) _, err = res.Write(json) require.NoError(t, err) })) defer testServer.Close() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.EnableRemoteMarketplace = true *cfg.PluginSettings.MarketplaceURL = testServer.URL }) pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin2"} manifest, _, err := client.InstallMarketplacePlugin(context.Background(), 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(context.Background(), 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) }, "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) _, err = res.Write(json) require.NoError(t, err) })) defer testServer.Close() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.EnableRemoteMarketplace = true *cfg.PluginSettings.MarketplaceURL = testServer.URL }) pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin2", Version: "9.9.9"} manifest, _, err := client.InstallMarketplacePlugin(context.Background(), 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(context.Background(), 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 testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { licenseType, ok := req.URL.Query()["enterprise_plugins"] require.True(t, ok) require.Len(t, licenseType, 1) require.Equal(t, "false", licenseType[0]) res.WriteHeader(http.StatusOK) json, err := json.Marshal([]*model.MarketplacePlugin{samplePlugins[0]}) require.NoError(t, err) _, err = res.Write(json) require.NoError(t, err) requestHandled = true })) defer func() { testServer.Close() }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.EnableRemoteMarketplace = true *cfg.PluginSettings.MarketplaceURL = testServer.URL }) pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin"} manifest, resp, err := client.InstallMarketplacePlugin(context.Background(), pRequest) require.Error(t, err) CheckInternalErrorStatus(t, resp) require.Nil(t, manifest) assert.True(t, requestHandled) }, "verify EnterprisePlugins is false for TE") th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { requestHandled := false testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { licenseType, ok := req.URL.Query()["enterprise_plugins"] require.True(t, ok) require.Len(t, licenseType, 1) require.Equal(t, "false", licenseType[0]) res.WriteHeader(http.StatusOK) json, err := json.Marshal([]*model.MarketplacePlugin{}) require.NoError(t, err) _, err = res.Write(json) require.NoError(t, err) requestHandled = true })) defer func() { testServer.Close() }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.EnableRemoteMarketplace = true *cfg.PluginSettings.MarketplaceURL = testServer.URL }) l := model.NewTestLicense() // model.NewTestLicense generates a E20 license *l.Features.EnterprisePlugins = false th.App.Srv().SetLicense(l) pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin"} manifest, resp, err := client.InstallMarketplacePlugin(context.Background(), pRequest) require.Error(t, err) CheckInternalErrorStatus(t, resp) require.Nil(t, manifest) assert.True(t, requestHandled) }, "verify EnterprisePlugins is false for E10") th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { requestHandled := false testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { licenseType, ok := req.URL.Query()["enterprise_plugins"] require.True(t, ok) require.Len(t, licenseType, 1) require.Equal(t, "true", licenseType[0]) res.WriteHeader(http.StatusOK) json, err := json.Marshal([]*model.MarketplacePlugin{}) require.NoError(t, err) _, err = res.Write(json) require.NoError(t, err) requestHandled = true })) defer func() { testServer.Close() }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.EnableMarketplace = true *cfg.PluginSettings.MarketplaceURL = testServer.URL }) th.App.Srv().SetLicense(model.NewTestLicense("enterprise_plugins")) pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin"} manifest, resp, err := client.InstallMarketplacePlugin(context.Background(), pRequest) require.Error(t, err) CheckInternalErrorStatus(t, resp) require.Nil(t, manifest) assert.True(t, requestHandled) }, "verify EnterprisePlugins is true for E20") } func TestInstallMarketplacePluginPrepackagedDisabled(t *testing.T) { mainHelper.Parallel(t) path, _ := fileutils.FindDir("tests") signatureFilename := "testplugin2.tar.gz.sig" signatureFileReader, err := os.Open(filepath.Join(path, signatureFilename)) require.NoError(t, err) sigFile, err := io.ReadAll(signatureFileReader) require.NoError(t, err) pluginSignature := base64.StdEncoding.EncodeToString(sigFile) tarData, err := os.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) _, err = res.Write(tarData) require.NoError(t, err) })) defer pluginServer.Close() samplePlugins := []*model.MarketplacePlugin{ { BaseMarketplacePlugin: &model.BaseMarketplacePlugin{ HomepageURL: "https://example.com/mattermost/mattermost-plugin-nps", IconData: "https://example.com/icon.svg", DownloadURL: pluginServer.URL, Manifest: &model.Manifest{ Id: "testplugin2", Name: "testplugin2", Description: "a second plugin", Version: "1.2.2", MinServerVersion: "", }, }, InstalledVersion: "", }, { BaseMarketplacePlugin: &model.BaseMarketplacePlugin{ HomepageURL: "https://example.com/mattermost/mattermost-plugin-nps", IconData: "https://example.com/icon.svg", DownloadURL: pluginServer.URL, Manifest: &model.Manifest{ Id: "testplugin2", Name: "testplugin2", Description: "a second plugin", Version: "1.2.3", MinServerVersion: "", }, Signature: pluginSignature, }, InstalledVersion: "", }, } 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 = testlib.CopyFile(filepath.Join(path, "testplugin.tar.gz"), filepath.Join(prepackagedPluginsDir, "testplugin.tar.gz")) require.NoError(t, err) err = testlib.CopyFile(filepath.Join(path, "testplugin.tar.gz.asc"), filepath.Join(prepackagedPluginsDir, "testplugin.tar.gz.sig")) require.NoError(t, err) th := SetupConfig(t, func(cfg *model.Config) { // Disable auto-installing prepackaged plugins *cfg.PluginSettings.AutomaticPrepackagedPlugins = false cfg.PluginSettings.SignaturePublicKeyFiles = []string{ filepath.Join(path, "development-private-key.asc"), } }).InitBasic() defer th.TearDown() th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { expectedSignaturePath := filepath.Join(prepackagedPluginsDir, "testplugin.tar.gz.sig") 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) var out []byte // Return something if testplugin2 or no specific plugin is requested pluginID := req.URL.Query().Get("plugin_id") if pluginID == "" || pluginID == samplePlugins[1].Manifest.Id { out, err = json.Marshal([]*model.MarketplacePlugin{samplePlugins[1]}) require.NoError(t, err) } _, err = res.Write(out) require.NoError(t, err) })) 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, _, err := client.GetPlugins(context.Background()) require.NoError(t, err) require.Len(t, pluginsResp.Active, 0) require.Len(t, pluginsResp.Inactive, 0) t.Run("Should fail to install unknown prepackaged plugin", func(t *testing.T) { pRequest := &model.InstallMarketplacePluginRequest{Id: "testpluginXX"} manifest, resp, err := client.InstallMarketplacePlugin(context.Background(), pRequest) require.Error(t, err) 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, expectedSignaturePath, plugins[0].SignaturePath) pluginsResp, _, err = client.GetPlugins(context.Background()) require.NoError(t, err) require.Len(t, pluginsResp.Active, 0) require.Len(t, pluginsResp.Inactive, 0) }) t.Run("Install prepackaged plugin with Marketplace disabled", func(t *testing.T) { pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin"} manifest, _, err := client.InstallMarketplacePlugin(context.Background(), pRequest) require.NoError(t, err) require.NotNil(t, manifest) require.Equal(t, "testplugin", manifest.Id) require.Equal(t, "0.0.1", manifest.Version) t.Cleanup(func() { _, err = client.RemovePlugin(context.Background(), manifest.Id) require.NoError(t, err) }) pluginsResp, _, err = client.GetPlugins(context.Background()) require.NoError(t, err) require.Len(t, pluginsResp.Active, 0) require.Equal(t, pluginsResp.Inactive, []*model.PluginInfo{{ Manifest: *manifest, }}) }) t.Run("Try to install remote marketplace plugin while Marketplace is disabled", func(t *testing.T) { pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin2"} manifest, resp, err := client.InstallMarketplacePlugin(context.Background(), pRequest) require.Error(t, err) 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 }) t.Run("Install prepackaged, not listed plugin with Marketplace enabled", func(t *testing.T) { pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin"} manifest, _, err := client.InstallMarketplacePlugin(context.Background(), pRequest) require.NoError(t, err) t.Cleanup(func() { _, err = client.RemovePlugin(context.Background(), manifest.Id) require.NoError(t, err) }) require.NotNil(t, manifest) assert.Equal(t, "testplugin", manifest.Id) assert.Equal(t, "0.0.1", manifest.Version) }) t.Run("Install both a prepackaged and a Marketplace plugin", func(t *testing.T) { pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin"} manifest1, _, err := client.InstallMarketplacePlugin(context.Background(), pRequest) require.NoError(t, err) require.NotNil(t, manifest1) assert.Equal(t, "testplugin", manifest1.Id) assert.Equal(t, "0.0.1", manifest1.Version) t.Cleanup(func() { _, err = client.RemovePlugin(context.Background(), manifest1.Id) require.NoError(t, err) }) pRequest = &model.InstallMarketplacePluginRequest{Id: "testplugin2"} manifest2, _, err := client.InstallMarketplacePlugin(context.Background(), pRequest) require.NoError(t, err) require.NotNil(t, manifest2) require.Equal(t, "testplugin2", manifest2.Id) require.Equal(t, "1.2.3", manifest2.Version) t.Cleanup(func() { _, err = client.RemovePlugin(context.Background(), manifest2.Id) require.NoError(t, err) }) pluginsResp, _, err = client.GetPlugins(context.Background()) require.NoError(t, err) require.Len(t, pluginsResp.Active, 0) require.ElementsMatch(t, pluginsResp.Inactive, []*model.PluginInfo{ { Manifest: *manifest1, }, { Manifest: *manifest2, }, }) }) }) }) 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 = testlib.CopyFile(filepath.Join(path, "testplugin.tar.gz"), filepath.Join(prepackagedPluginsDir, "testplugin.tar.gz")) require.NoError(t, err) th := SetupConfig(t, func(cfg *model.Config) { // Disable auto-installing prepackaged plugins *cfg.PluginSettings.AutomaticPrepackagedPlugins = false cfg.PluginSettings.SignaturePublicKeyFiles = []string{ filepath.Join(path, "development-private-key.asc"), } }).InitBasic() defer th.TearDown() 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) mPlugins := []*model.MarketplacePlugin{samplePlugins[0]} require.Empty(t, mPlugins[0].Signature) res.WriteHeader(http.StatusOK) var out []byte out, err = json.Marshal(mPlugins) require.NoError(t, err) _, err = res.Write(out) require.NoError(t, err) })) 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, 0) pluginsResp, _, err := client.GetPlugins(context.Background()) require.NoError(t, err) require.Len(t, pluginsResp.Active, 0) require.Len(t, pluginsResp.Inactive, 0) pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin"} manifest, resp, err := client.InstallMarketplacePlugin(context.Background(), pRequest) require.Error(t, err) CheckInternalErrorStatus(t, resp) require.Nil(t, manifest) pluginsResp, _, err = client.GetPlugins(context.Background()) require.NoError(t, err) require.Len(t, pluginsResp.Active, 0) require.Len(t, pluginsResp.Inactive, 0) pRequest = &model.InstallMarketplacePluginRequest{Id: "testplugin2"} manifest, resp, err = client.InstallMarketplacePlugin(context.Background(), pRequest) require.Error(t, err) CheckInternalErrorStatus(t, resp) require.Nil(t, manifest) pluginsResp, _, err = client.GetPlugins(context.Background()) require.NoError(t, err) require.Len(t, pluginsResp.Active, 0) require.Len(t, pluginsResp.Inactive, 0) }) }) } func findClusterMessages(event model.ClusterEvent, msgs []*model.ClusterMessage) []*model.ClusterMessage { var result []*model.ClusterMessage for _, msg := range msgs { if msg.Event == event { result = append(result, msg) } } return result } func TestPluginWebSocketSession(t *testing.T) { mainHelper.Parallel(t) th := Setup(t).InitBasic() defer th.TearDown() pluginID := "com.mattermost.websocket_session_test" // Compile plugin fullPath := filepath.Join(server.GetPackagePath(), "channels", "app", "plugin_api_tests", "manual.test_websocket_session", "main.go") pluginCode, err := os.ReadFile(fullPath) require.NoError(t, err) require.NotEmpty(t, pluginCode) pluginDir, err := filepath.Abs(*th.App.Config().PluginSettings.Directory) require.NoError(t, err) backend := filepath.Join(pluginDir, pluginID, "backend.exe") utils.CompileGo(t, string(pluginCode), backend) err = os.WriteFile(filepath.Join(pluginDir, pluginID, "plugin.json"), []byte(`{"id": "`+pluginID+`", "server": {"executable": "backend.exe"}}`), 0600) require.NoError(t, err) // Activate the plugin manifest, activated, reterr := th.App.GetPluginsEnvironment().Activate(pluginID) require.NoError(t, reterr) require.NotNil(t, manifest) require.True(t, activated) // Connect through WebSocket and send a message reqURL := fmt.Sprintf("ws://localhost:%d", th.Server.ListenAddr.Port) wsc, err := model.NewWebSocketClient4(reqURL, th.Client.AuthToken) require.NoError(t, err) require.NotNil(t, wsc) wsc.Listen() defer wsc.Close() resp := <-wsc.ResponseChannel require.Equal(t, resp.Status, model.StatusOk) wsc.SendMessage("custom_action", map[string]any{"value": "test"}) // Get session for user sessions, _, err := th.Client.GetSessions(context.Background(), th.BasicUser.Id, "") require.NoError(t, err) require.NotEmpty(t, sessions) // Verify the session has been set correctly. Check plugin code in // channels/app/plugin_api_tests/manual.test_websocket_session // // Here the MessageWillBePosted hook is used purely as a way to // communicate with the plugin side. hooks, err := th.App.GetPluginsEnvironment().HooksForPlugin(pluginID) require.NoError(t, err) require.NotNil(t, hooks) _, sessionID := hooks.MessageWillBePosted(nil, nil) require.Equal(t, sessions[0].Id, sessionID) } func TestPluginWebSocketRemoteAddress(t *testing.T) { mainHelper.Parallel(t) th := Setup(t).InitBasic() defer th.TearDown() pluginID := "com.mattermost.websocket_remote_address_test" // Compile plugin fullPath := filepath.Join(server.GetPackagePath(), "channels", "app", "plugin_api_tests", "manual.test_websocket_remote_address", "main.go") pluginCode, err := os.ReadFile(fullPath) require.NoError(t, err) require.NotEmpty(t, pluginCode) pluginDir, err := filepath.Abs(*th.App.Config().PluginSettings.Directory) require.NoError(t, err) backend := filepath.Join(pluginDir, pluginID, "backend.exe") utils.CompileGo(t, string(pluginCode), backend) err = os.WriteFile(filepath.Join(pluginDir, pluginID, "plugin.json"), []byte(`{"id": "`+pluginID+`", "server": {"executable": "backend.exe"}}`), 0600) require.NoError(t, err) // Activate the plugin manifest, activated, reterr := th.App.GetPluginsEnvironment().Activate(pluginID) require.NoError(t, reterr) require.NotNil(t, manifest) require.True(t, activated) // Connect through WebSocket and send a message reqURL := fmt.Sprintf("ws://localhost:%d", th.Server.ListenAddr.Port) wsc, err := model.NewWebSocketClient4(reqURL, th.Client.AuthToken) require.NoError(t, err) require.NotNil(t, wsc) wsc.Listen() defer wsc.Close() resp := <-wsc.ResponseChannel require.Equal(t, resp.Status, model.StatusOk) wsc.SendMessage("custom_action", map[string]any{"value": "test"}) // Verify the remote address has been set correctly. Check plugin code in // channels/app/plugin_api_tests/manual.test_websocket_remote_address // // Here the MessageWillBePosted hook is used purely as a way to // communicate with the plugin side. hooks, err := th.App.GetPluginsEnvironment().HooksForPlugin(pluginID) require.NoError(t, err) require.NotNil(t, hooks) _, remoteAddr := hooks.MessageWillBePosted(nil, nil) require.NotEmpty(t, remoteAddr) }