github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/app/plugin_test.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"bytes"
     8  	"crypto/sha256"
     9  	"encoding/base64"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"net/http"
    13  	"net/http/httptest"
    14  	"os"
    15  	"path/filepath"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/gorilla/mux"
    20  	"github.com/stretchr/testify/assert"
    21  	"github.com/stretchr/testify/require"
    22  
    23  	"github.com/mattermost/mattermost-server/v5/mlog"
    24  	"github.com/mattermost/mattermost-server/v5/model"
    25  	"github.com/mattermost/mattermost-server/v5/plugin"
    26  	"github.com/mattermost/mattermost-server/v5/testlib"
    27  	"github.com/mattermost/mattermost-server/v5/utils/fileutils"
    28  )
    29  
    30  func getHashedKey(key string) string {
    31  	hash := sha256.New()
    32  	hash.Write([]byte(key))
    33  	return base64.StdEncoding.EncodeToString(hash.Sum(nil))
    34  }
    35  
    36  func TestPluginKeyValueStore(t *testing.T) {
    37  	th := Setup(t)
    38  	defer th.TearDown()
    39  
    40  	pluginId := "testpluginid"
    41  
    42  	defer func() {
    43  		assert.Nil(t, th.App.DeletePluginKey(pluginId, "key"))
    44  		assert.Nil(t, th.App.DeletePluginKey(pluginId, "key2"))
    45  		assert.Nil(t, th.App.DeletePluginKey(pluginId, "key3"))
    46  		assert.Nil(t, th.App.DeletePluginKey(pluginId, "key4"))
    47  	}()
    48  
    49  	assert.Nil(t, th.App.SetPluginKey(pluginId, "key", []byte("test")))
    50  	ret, err := th.App.GetPluginKey(pluginId, "key")
    51  	assert.Nil(t, err)
    52  	assert.Equal(t, []byte("test"), ret)
    53  
    54  	// Test inserting over existing entries
    55  	assert.Nil(t, th.App.SetPluginKey(pluginId, "key", []byte("test2")))
    56  	ret, err = th.App.GetPluginKey(pluginId, "key")
    57  	assert.Nil(t, err)
    58  	assert.Equal(t, []byte("test2"), ret)
    59  
    60  	// Test getting non-existent key
    61  	ret, err = th.App.GetPluginKey(pluginId, "notakey")
    62  	assert.Nil(t, err)
    63  	assert.Nil(t, ret)
    64  
    65  	// Test deleting non-existent keys.
    66  	assert.Nil(t, th.App.DeletePluginKey(pluginId, "notrealkey"))
    67  
    68  	// Verify behaviour for the old approach that involved storing the hashed keys.
    69  	hashedKey2 := getHashedKey("key2")
    70  	kv := &model.PluginKeyValue{
    71  		PluginId: pluginId,
    72  		Key:      hashedKey2,
    73  		Value:    []byte("test"),
    74  		ExpireAt: 0,
    75  	}
    76  
    77  	_, nErr := th.App.Srv().Store.Plugin().SaveOrUpdate(kv)
    78  	assert.NoError(t, nErr)
    79  
    80  	// Test fetch by keyname (this key does not exist but hashed key will be used for lookup)
    81  	ret, err = th.App.GetPluginKey(pluginId, "key2")
    82  	assert.Nil(t, err)
    83  	assert.Equal(t, kv.Value, ret)
    84  
    85  	// Test fetch by hashed keyname
    86  	ret, err = th.App.GetPluginKey(pluginId, hashedKey2)
    87  	assert.Nil(t, err)
    88  	assert.Equal(t, kv.Value, ret)
    89  
    90  	// Test ListKeys
    91  	assert.Nil(t, th.App.SetPluginKey(pluginId, "key3", []byte("test3")))
    92  	assert.Nil(t, th.App.SetPluginKey(pluginId, "key4", []byte("test4")))
    93  
    94  	list, err := th.App.ListPluginKeys(pluginId, 0, 1)
    95  	assert.Nil(t, err)
    96  	assert.Equal(t, []string{"key"}, list)
    97  
    98  	list, err = th.App.ListPluginKeys(pluginId, 1, 1)
    99  	assert.Nil(t, err)
   100  	assert.Equal(t, []string{"key3"}, list)
   101  
   102  	list, err = th.App.ListPluginKeys(pluginId, 0, 4)
   103  	assert.Nil(t, err)
   104  	assert.Equal(t, []string{"key", "key3", "key4", hashedKey2}, list)
   105  
   106  	list, err = th.App.ListPluginKeys(pluginId, 0, 2)
   107  	assert.Nil(t, err)
   108  	assert.Equal(t, []string{"key", "key3"}, list)
   109  
   110  	list, err = th.App.ListPluginKeys(pluginId, 1, 2)
   111  	assert.Nil(t, err)
   112  	assert.Equal(t, []string{"key4", hashedKey2}, list)
   113  
   114  	list, err = th.App.ListPluginKeys(pluginId, 2, 2)
   115  	assert.Nil(t, err)
   116  	assert.Equal(t, []string{}, list)
   117  
   118  	// List Keys bad input
   119  	list, err = th.App.ListPluginKeys(pluginId, 0, 0)
   120  	assert.Nil(t, err)
   121  	assert.Equal(t, []string{"key", "key3", "key4", hashedKey2}, list)
   122  
   123  	list, err = th.App.ListPluginKeys(pluginId, 0, -1)
   124  	assert.Nil(t, err)
   125  	assert.Equal(t, []string{"key", "key3", "key4", hashedKey2}, list)
   126  
   127  	list, err = th.App.ListPluginKeys(pluginId, -1, 1)
   128  	assert.Nil(t, err)
   129  	assert.Equal(t, []string{"key"}, list)
   130  
   131  	list, err = th.App.ListPluginKeys(pluginId, -1, 0)
   132  	assert.Nil(t, err)
   133  	assert.Equal(t, []string{"key", "key3", "key4", hashedKey2}, list)
   134  }
   135  
   136  func TestPluginKeyValueStoreCompareAndSet(t *testing.T) {
   137  	th := Setup(t)
   138  	defer th.TearDown()
   139  
   140  	pluginId := "testpluginid"
   141  
   142  	defer func() {
   143  		assert.Nil(t, th.App.DeletePluginKey(pluginId, "key"))
   144  	}()
   145  
   146  	// Set using Set api for key2
   147  	assert.Nil(t, th.App.SetPluginKey(pluginId, "key2", []byte("test")))
   148  	ret, err := th.App.GetPluginKey(pluginId, "key2")
   149  	assert.Nil(t, err)
   150  	assert.Equal(t, []byte("test"), ret)
   151  
   152  	// Attempt to insert value for key2
   153  	updated, err := th.App.CompareAndSetPluginKey(pluginId, "key2", nil, []byte("test2"))
   154  	assert.Nil(t, err)
   155  	assert.False(t, updated)
   156  	ret, err = th.App.GetPluginKey(pluginId, "key2")
   157  	assert.Nil(t, err)
   158  	assert.Equal(t, []byte("test"), ret)
   159  
   160  	// Insert new value for key
   161  	updated, err = th.App.CompareAndSetPluginKey(pluginId, "key", nil, []byte("test"))
   162  	assert.Nil(t, err)
   163  	assert.True(t, updated)
   164  	ret, err = th.App.GetPluginKey(pluginId, "key")
   165  	assert.Nil(t, err)
   166  	assert.Equal(t, []byte("test"), ret)
   167  
   168  	// Should fail to insert again
   169  	updated, err = th.App.CompareAndSetPluginKey(pluginId, "key", nil, []byte("test3"))
   170  	assert.Nil(t, err)
   171  	assert.False(t, updated)
   172  	ret, err = th.App.GetPluginKey(pluginId, "key")
   173  	assert.Nil(t, err)
   174  	assert.Equal(t, []byte("test"), ret)
   175  
   176  	// Test updating using incorrect old value
   177  	updated, err = th.App.CompareAndSetPluginKey(pluginId, "key", []byte("oldvalue"), []byte("test3"))
   178  	assert.Nil(t, err)
   179  	assert.False(t, updated)
   180  	ret, err = th.App.GetPluginKey(pluginId, "key")
   181  	assert.Nil(t, err)
   182  	assert.Equal(t, []byte("test"), ret)
   183  
   184  	// Test updating using correct old value
   185  	updated, err = th.App.CompareAndSetPluginKey(pluginId, "key", []byte("test"), []byte("test2"))
   186  	assert.Nil(t, err)
   187  	assert.True(t, updated)
   188  	ret, err = th.App.GetPluginKey(pluginId, "key")
   189  	assert.Nil(t, err)
   190  	assert.Equal(t, []byte("test2"), ret)
   191  }
   192  
   193  func TestPluginKeyValueStoreSetWithOptionsJSON(t *testing.T) {
   194  	pluginId := "testpluginid"
   195  
   196  	t.Run("storing a value without providing options works", func(t *testing.T) {
   197  		th := Setup(t)
   198  		defer th.TearDown()
   199  
   200  		result, err := th.App.SetPluginKeyWithOptions(pluginId, "key", []byte("value-1"), model.PluginKVSetOptions{})
   201  		assert.True(t, result)
   202  		assert.Nil(t, err)
   203  
   204  		// and I can get it back!
   205  		ret, err := th.App.GetPluginKey(pluginId, "key")
   206  		assert.Nil(t, err)
   207  		assert.Equal(t, []byte(`value-1`), ret)
   208  	})
   209  
   210  	t.Run("test that setting it atomic when it doesn't match doesn't change anything", func(t *testing.T) {
   211  		th := Setup(t)
   212  		defer th.TearDown()
   213  
   214  		err := th.App.SetPluginKey(pluginId, "key", []byte("value-1"))
   215  		require.Nil(t, err)
   216  
   217  		result, err := th.App.SetPluginKeyWithOptions(pluginId, "key", []byte("value-3"), model.PluginKVSetOptions{
   218  			Atomic:   true,
   219  			OldValue: []byte("value-2"),
   220  		})
   221  		assert.False(t, result)
   222  		assert.Nil(t, err)
   223  
   224  		// test that the value didn't change
   225  		ret, err := th.App.GetPluginKey(pluginId, "key")
   226  		assert.Nil(t, err)
   227  		assert.Equal(t, []byte(`value-1`), ret)
   228  	})
   229  
   230  	t.Run("test the atomic change with the proper old value", func(t *testing.T) {
   231  		th := Setup(t)
   232  		defer th.TearDown()
   233  
   234  		err := th.App.SetPluginKey(pluginId, "key", []byte("value-2"))
   235  		require.Nil(t, err)
   236  
   237  		result, err := th.App.SetPluginKeyWithOptions(pluginId, "key", []byte("value-3"), model.PluginKVSetOptions{
   238  			Atomic:   true,
   239  			OldValue: []byte("value-2"),
   240  		})
   241  		assert.True(t, result)
   242  		assert.Nil(t, err)
   243  
   244  		// test that the value did change
   245  		ret, err := th.App.GetPluginKey(pluginId, "key")
   246  		assert.Nil(t, err)
   247  		assert.Equal(t, []byte(`value-3`), ret)
   248  	})
   249  
   250  	t.Run("when new value is nil and old value matches with the current, it should delete the currently set value", func(t *testing.T) {
   251  		th := Setup(t)
   252  		defer th.TearDown()
   253  
   254  		// first set a value.
   255  		result, err := th.App.SetPluginKeyWithOptions(pluginId, "nil-test-key-2", []byte("value-1"), model.PluginKVSetOptions{})
   256  		require.Nil(t, err)
   257  		require.True(t, result)
   258  
   259  		// now it should delete the set value.
   260  		result, err = th.App.SetPluginKeyWithOptions(pluginId, "nil-test-key-2", nil, model.PluginKVSetOptions{
   261  			Atomic:   true,
   262  			OldValue: []byte("value-1"),
   263  		})
   264  		assert.Nil(t, err)
   265  		assert.True(t, result)
   266  
   267  		ret, err := th.App.GetPluginKey(pluginId, "nil-test-key-2")
   268  		assert.Nil(t, err)
   269  		assert.Nil(t, ret)
   270  	})
   271  
   272  	t.Run("when new value is nil and there is a value set for the key already, it should delete the currently set value", func(t *testing.T) {
   273  		th := Setup(t)
   274  		defer th.TearDown()
   275  
   276  		// first set a value.
   277  		result, err := th.App.SetPluginKeyWithOptions(pluginId, "nil-test-key-3", []byte("value-1"), model.PluginKVSetOptions{})
   278  		require.Nil(t, err)
   279  		require.True(t, result)
   280  
   281  		// now it should delete the set value.
   282  		result, err = th.App.SetPluginKeyWithOptions(pluginId, "nil-test-key-3", nil, model.PluginKVSetOptions{})
   283  		assert.Nil(t, err)
   284  		assert.True(t, result)
   285  
   286  		// verify a nil value is returned
   287  		ret, err := th.App.GetPluginKey(pluginId, "nil-test-key-3")
   288  		assert.Nil(t, err)
   289  		assert.Nil(t, ret)
   290  
   291  		// verify the row is actually gone
   292  		list, err := th.App.ListPluginKeys(pluginId, 0, 1)
   293  		assert.Nil(t, err)
   294  		assert.Empty(t, list)
   295  	})
   296  
   297  	t.Run("when old value is nil and there is no value set for the key before, it should set the new value", func(t *testing.T) {
   298  		th := Setup(t)
   299  		defer th.TearDown()
   300  
   301  		result, err := th.App.SetPluginKeyWithOptions(pluginId, "nil-test-key-4", []byte("value-1"), model.PluginKVSetOptions{
   302  			Atomic:   true,
   303  			OldValue: nil,
   304  		})
   305  		assert.Nil(t, err)
   306  		assert.True(t, result)
   307  
   308  		ret, err := th.App.GetPluginKey(pluginId, "nil-test-key-4")
   309  		assert.Nil(t, err)
   310  		assert.Equal(t, []byte("value-1"), ret)
   311  	})
   312  
   313  	t.Run("test that value is set and unset with ExpireInSeconds", func(t *testing.T) {
   314  		th := Setup(t)
   315  		defer th.TearDown()
   316  
   317  		result, err := th.App.SetPluginKeyWithOptions(pluginId, "key", []byte("value-1"), model.PluginKVSetOptions{
   318  			ExpireInSeconds: 1,
   319  		})
   320  		assert.True(t, result)
   321  		assert.Nil(t, err)
   322  
   323  		// test that the value is set
   324  		ret, err := th.App.GetPluginKey(pluginId, "key")
   325  		assert.Nil(t, err)
   326  		assert.Equal(t, []byte(`value-1`), ret)
   327  
   328  		// test that the value is not longer
   329  		time.Sleep(1500 * time.Millisecond)
   330  
   331  		ret, err = th.App.GetPluginKey(pluginId, "key")
   332  		assert.Nil(t, err)
   333  		assert.Nil(t, ret)
   334  	})
   335  }
   336  
   337  func TestServePluginRequest(t *testing.T) {
   338  	th := Setup(t)
   339  	defer th.TearDown()
   340  
   341  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false })
   342  
   343  	w := httptest.NewRecorder()
   344  	r := httptest.NewRequest("GET", "/plugins/foo/bar", nil)
   345  	th.App.ServePluginRequest(w, r)
   346  	assert.Equal(t, http.StatusNotImplemented, w.Result().StatusCode)
   347  }
   348  
   349  func TestPrivateServePluginRequest(t *testing.T) {
   350  	th := Setup(t)
   351  	defer th.TearDown()
   352  
   353  	testCases := []struct {
   354  		Description string
   355  		ConfigFunc  func(cfg *model.Config)
   356  		URL         string
   357  		ExpectedURL string
   358  	}{
   359  		{
   360  			"no subpath",
   361  			func(cfg *model.Config) {},
   362  			"/plugins/id/endpoint",
   363  			"/endpoint",
   364  		},
   365  		{
   366  			"subpath",
   367  			func(cfg *model.Config) { *cfg.ServiceSettings.SiteURL += "/subpath" },
   368  			"/subpath/plugins/id/endpoint",
   369  			"/endpoint",
   370  		},
   371  	}
   372  
   373  	for _, testCase := range testCases {
   374  		t.Run(testCase.Description, func(t *testing.T) {
   375  			th.App.UpdateConfig(testCase.ConfigFunc)
   376  			expectedBody := []byte("body")
   377  			request := httptest.NewRequest(http.MethodGet, testCase.URL, bytes.NewReader(expectedBody))
   378  			recorder := httptest.NewRecorder()
   379  
   380  			handler := func(context *plugin.Context, w http.ResponseWriter, r *http.Request) {
   381  				assert.Equal(t, testCase.ExpectedURL, r.URL.Path)
   382  
   383  				body, _ := ioutil.ReadAll(r.Body)
   384  				assert.Equal(t, expectedBody, body)
   385  			}
   386  
   387  			request = mux.SetURLVars(request, map[string]string{"plugin_id": "id"})
   388  
   389  			th.App.servePluginRequest(recorder, request, handler)
   390  		})
   391  	}
   392  
   393  }
   394  
   395  func TestHandlePluginRequest(t *testing.T) {
   396  	th := Setup(t).InitBasic()
   397  	defer th.TearDown()
   398  
   399  	th.App.UpdateConfig(func(cfg *model.Config) {
   400  		*cfg.PluginSettings.Enable = false
   401  		*cfg.ServiceSettings.EnableUserAccessTokens = true
   402  	})
   403  
   404  	token, err := th.App.CreateUserAccessToken(&model.UserAccessToken{
   405  		UserId: th.BasicUser.Id,
   406  	})
   407  	require.Nil(t, err)
   408  
   409  	var assertions func(*http.Request)
   410  	router := mux.NewRouter()
   411  	router.HandleFunc("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}/{anything:.*}", func(_ http.ResponseWriter, r *http.Request) {
   412  		th.App.servePluginRequest(nil, r, func(_ *plugin.Context, _ http.ResponseWriter, r *http.Request) {
   413  			assertions(r)
   414  		})
   415  	})
   416  
   417  	r := httptest.NewRequest("GET", "/plugins/foo/bar", nil)
   418  	r.Header.Add("Authorization", "Bearer "+token.Token)
   419  	assertions = func(r *http.Request) {
   420  		assert.Equal(t, "/bar", r.URL.Path)
   421  		assert.Equal(t, th.BasicUser.Id, r.Header.Get("Mattermost-User-Id"))
   422  	}
   423  	router.ServeHTTP(nil, r)
   424  
   425  	r = httptest.NewRequest("GET", "/plugins/foo/bar?a=b&access_token="+token.Token+"&c=d", nil)
   426  	assertions = func(r *http.Request) {
   427  		assert.Equal(t, "/bar", r.URL.Path)
   428  		assert.Equal(t, "a=b&c=d", r.URL.RawQuery)
   429  		assert.Equal(t, th.BasicUser.Id, r.Header.Get("Mattermost-User-Id"))
   430  	}
   431  	router.ServeHTTP(nil, r)
   432  
   433  	r = httptest.NewRequest("GET", "/plugins/foo/bar?a=b&access_token=asdf&c=d", nil)
   434  	assertions = func(r *http.Request) {
   435  		assert.Equal(t, "/bar", r.URL.Path)
   436  		assert.Equal(t, "a=b&c=d", r.URL.RawQuery)
   437  		assert.Empty(t, r.Header.Get("Mattermost-User-Id"))
   438  	}
   439  	router.ServeHTTP(nil, r)
   440  }
   441  
   442  func TestGetPluginStatusesDisabled(t *testing.T) {
   443  	th := Setup(t)
   444  	defer th.TearDown()
   445  
   446  	th.App.UpdateConfig(func(cfg *model.Config) {
   447  		*cfg.PluginSettings.Enable = false
   448  	})
   449  
   450  	_, err := th.App.GetPluginStatuses()
   451  	require.NotNil(t, err)
   452  	require.EqualError(t, err, "GetPluginStatuses: Plugins have been disabled. Please check your logs for details., ")
   453  }
   454  
   455  func TestGetPluginStatuses(t *testing.T) {
   456  	th := Setup(t)
   457  	defer th.TearDown()
   458  
   459  	th.App.UpdateConfig(func(cfg *model.Config) {
   460  		*cfg.PluginSettings.Enable = true
   461  	})
   462  
   463  	pluginStatuses, err := th.App.GetPluginStatuses()
   464  	require.Nil(t, err)
   465  	require.NotNil(t, pluginStatuses)
   466  }
   467  
   468  func TestPluginSync(t *testing.T) {
   469  	th := Setup(t)
   470  	defer th.TearDown()
   471  
   472  	testCases := []struct {
   473  		Description string
   474  		ConfigFunc  func(cfg *model.Config)
   475  	}{
   476  		{
   477  			"local",
   478  			func(cfg *model.Config) {
   479  				cfg.FileSettings.DriverName = model.NewString(model.IMAGE_DRIVER_LOCAL)
   480  			},
   481  		},
   482  		{
   483  			"s3",
   484  			func(cfg *model.Config) {
   485  				s3Host := os.Getenv("CI_MINIO_HOST")
   486  				if s3Host == "" {
   487  					s3Host = "localhost"
   488  				}
   489  
   490  				s3Port := os.Getenv("CI_MINIO_PORT")
   491  				if s3Port == "" {
   492  					s3Port = "9000"
   493  				}
   494  
   495  				s3Endpoint := fmt.Sprintf("%s:%s", s3Host, s3Port)
   496  				cfg.FileSettings.DriverName = model.NewString(model.IMAGE_DRIVER_S3)
   497  				cfg.FileSettings.AmazonS3AccessKeyId = model.NewString(model.MINIO_ACCESS_KEY)
   498  				cfg.FileSettings.AmazonS3SecretAccessKey = model.NewString(model.MINIO_SECRET_KEY)
   499  				cfg.FileSettings.AmazonS3Bucket = model.NewString(model.MINIO_BUCKET)
   500  				cfg.FileSettings.AmazonS3PathPrefix = model.NewString("")
   501  				cfg.FileSettings.AmazonS3Endpoint = model.NewString(s3Endpoint)
   502  				cfg.FileSettings.AmazonS3Region = model.NewString("")
   503  				cfg.FileSettings.AmazonS3SSL = model.NewBool(false)
   504  
   505  			},
   506  		},
   507  	}
   508  
   509  	for _, testCase := range testCases {
   510  		t.Run(testCase.Description, func(t *testing.T) {
   511  			th.App.UpdateConfig(func(cfg *model.Config) {
   512  				*cfg.PluginSettings.Enable = true
   513  				testCase.ConfigFunc(cfg)
   514  			})
   515  
   516  			env := th.App.GetPluginsEnvironment()
   517  			require.NotNil(t, env)
   518  
   519  			path, _ := fileutils.FindDir("tests")
   520  
   521  			t.Run("new bundle in the file store", func(t *testing.T) {
   522  				th.App.UpdateConfig(func(cfg *model.Config) {
   523  					*cfg.PluginSettings.RequirePluginSignature = false
   524  				})
   525  
   526  				fileReader, err := os.Open(filepath.Join(path, "testplugin.tar.gz"))
   527  				require.NoError(t, err)
   528  				defer fileReader.Close()
   529  
   530  				_, appErr := th.App.WriteFile(fileReader, th.App.getBundleStorePath("testplugin"))
   531  				checkNoError(t, appErr)
   532  
   533  				appErr = th.App.SyncPlugins()
   534  				checkNoError(t, appErr)
   535  
   536  				// Check if installed
   537  				pluginStatus, err := env.Statuses()
   538  				require.NoError(t, err)
   539  				require.Len(t, pluginStatus, 1)
   540  				require.Equal(t, pluginStatus[0].PluginId, "testplugin")
   541  			})
   542  
   543  			t.Run("bundle removed from the file store", func(t *testing.T) {
   544  				th.App.UpdateConfig(func(cfg *model.Config) {
   545  					*cfg.PluginSettings.RequirePluginSignature = false
   546  				})
   547  
   548  				appErr := th.App.RemoveFile(th.App.getBundleStorePath("testplugin"))
   549  				checkNoError(t, appErr)
   550  
   551  				appErr = th.App.SyncPlugins()
   552  				checkNoError(t, appErr)
   553  
   554  				// Check if removed
   555  				pluginStatus, err := env.Statuses()
   556  				require.NoError(t, err)
   557  				require.Empty(t, pluginStatus)
   558  			})
   559  
   560  			t.Run("plugin signatures required, no signature", func(t *testing.T) {
   561  				th.App.UpdateConfig(func(cfg *model.Config) {
   562  					*cfg.PluginSettings.RequirePluginSignature = true
   563  				})
   564  
   565  				pluginFileReader, err := os.Open(filepath.Join(path, "testplugin.tar.gz"))
   566  				require.NoError(t, err)
   567  				defer pluginFileReader.Close()
   568  				_, appErr := th.App.WriteFile(pluginFileReader, th.App.getBundleStorePath("testplugin"))
   569  				checkNoError(t, appErr)
   570  
   571  				appErr = th.App.SyncPlugins()
   572  				checkNoError(t, appErr)
   573  				pluginStatus, err := env.Statuses()
   574  				require.NoError(t, err)
   575  				require.Len(t, pluginStatus, 0)
   576  			})
   577  
   578  			t.Run("plugin signatures required, wrong signature", func(t *testing.T) {
   579  				th.App.UpdateConfig(func(cfg *model.Config) {
   580  					*cfg.PluginSettings.RequirePluginSignature = true
   581  				})
   582  
   583  				signatureFileReader, err := os.Open(filepath.Join(path, "testplugin2.tar.gz.sig"))
   584  				require.NoError(t, err)
   585  				defer signatureFileReader.Close()
   586  				_, appErr := th.App.WriteFile(signatureFileReader, th.App.getSignatureStorePath("testplugin"))
   587  				checkNoError(t, appErr)
   588  
   589  				appErr = th.App.SyncPlugins()
   590  				checkNoError(t, appErr)
   591  
   592  				pluginStatus, err := env.Statuses()
   593  				require.NoError(t, err)
   594  				require.Len(t, pluginStatus, 0)
   595  			})
   596  
   597  			t.Run("plugin signatures required, correct signature", func(t *testing.T) {
   598  				th.App.UpdateConfig(func(cfg *model.Config) {
   599  					*cfg.PluginSettings.RequirePluginSignature = true
   600  				})
   601  
   602  				key, err := os.Open(filepath.Join(path, "development-private-key.asc"))
   603  				require.NoError(t, err)
   604  				appErr := th.App.AddPublicKey("pub_key", key)
   605  				checkNoError(t, appErr)
   606  
   607  				signatureFileReader, err := os.Open(filepath.Join(path, "testplugin.tar.gz.sig"))
   608  				require.NoError(t, err)
   609  				defer signatureFileReader.Close()
   610  				_, appErr = th.App.WriteFile(signatureFileReader, th.App.getSignatureStorePath("testplugin"))
   611  				checkNoError(t, appErr)
   612  
   613  				appErr = th.App.SyncPlugins()
   614  				checkNoError(t, appErr)
   615  
   616  				pluginStatus, err := env.Statuses()
   617  				require.NoError(t, err)
   618  				require.Len(t, pluginStatus, 1)
   619  				require.Equal(t, pluginStatus[0].PluginId, "testplugin")
   620  
   621  				appErr = th.App.DeletePublicKey("pub_key")
   622  				checkNoError(t, appErr)
   623  
   624  				appErr = th.App.RemovePlugin("testplugin")
   625  				checkNoError(t, appErr)
   626  			})
   627  		})
   628  	}
   629  }
   630  
   631  func TestSyncPluginsActiveState(t *testing.T) {
   632  	th := Setup(t)
   633  	defer th.TearDown()
   634  
   635  	th.App.UpdateConfig(func(cfg *model.Config) {
   636  		*cfg.PluginSettings.Enable = true
   637  	})
   638  
   639  	env := th.App.GetPluginsEnvironment()
   640  	require.NotNil(t, env)
   641  
   642  	th.App.UpdateConfig(func(cfg *model.Config) {
   643  		*cfg.PluginSettings.RequirePluginSignature = false
   644  	})
   645  
   646  	path, _ := fileutils.FindDir("tests")
   647  	fileReader, err := os.Open(filepath.Join(path, "testplugin.tar.gz"))
   648  	require.NoError(t, err)
   649  	defer fileReader.Close()
   650  
   651  	_, appErr := th.App.WriteFile(fileReader, th.App.getBundleStorePath("testplugin"))
   652  	checkNoError(t, appErr)
   653  
   654  	// Sync with file store so the plugin environment has access to this plugin.
   655  	appErr = th.App.SyncPlugins()
   656  	checkNoError(t, appErr)
   657  
   658  	// Verify the plugin was installed and set to deactivated.
   659  	pluginStatus, err := env.Statuses()
   660  	require.NoError(t, err)
   661  	require.Len(t, pluginStatus, 1)
   662  	require.Equal(t, pluginStatus[0].PluginId, "testplugin")
   663  	require.Equal(t, pluginStatus[0].State, model.PluginStateNotRunning)
   664  
   665  	// Enable plugin by setting setting config. This implicitly calls SyncPluginsActiveState through a config listener.
   666  	th.App.UpdateConfig(func(cfg *model.Config) {
   667  		cfg.PluginSettings.PluginStates["testplugin"] = &model.PluginState{Enable: true}
   668  	})
   669  
   670  	// Verify the plugin was activated due to config change.
   671  	pluginStatus, err = env.Statuses()
   672  	require.NoError(t, err)
   673  	require.Len(t, pluginStatus, 1)
   674  	require.Equal(t, pluginStatus[0].PluginId, "testplugin")
   675  	require.Equal(t, pluginStatus[0].State, model.PluginStateRunning)
   676  
   677  	// Disable plugin by setting config. This implicitly calls SyncPluginsActiveState through a config listener.
   678  	th.App.UpdateConfig(func(cfg *model.Config) {
   679  		cfg.PluginSettings.PluginStates["testplugin"] = &model.PluginState{Enable: false}
   680  	})
   681  
   682  	// Verify the plugin was deactivated due to config change.
   683  	pluginStatus, err = env.Statuses()
   684  	require.NoError(t, err)
   685  	require.Len(t, pluginStatus, 1)
   686  	require.Equal(t, pluginStatus[0].PluginId, "testplugin")
   687  	require.Equal(t, pluginStatus[0].State, model.PluginStateNotRunning)
   688  }
   689  
   690  func TestPluginPanicLogs(t *testing.T) {
   691  	t.Run("should panic", func(t *testing.T) {
   692  		th := Setup(t).InitBasic()
   693  		defer th.TearDown()
   694  		tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
   695  			`
   696  		package main
   697  
   698  		import (
   699  			"github.com/mattermost/mattermost-server/v5/plugin"
   700  			"github.com/mattermost/mattermost-server/v5/model"
   701  		)
   702  
   703  		type MyPlugin struct {
   704  			plugin.MattermostPlugin
   705  		}
   706  
   707  		func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
   708  			panic("some text from panic")
   709  			return nil, ""
   710  		}
   711  
   712  		func main() {
   713  			plugin.ClientMain(&MyPlugin{})
   714  		}
   715  		`,
   716  		}, th.App, th.App.NewPluginAPI)
   717  
   718  		post := &model.Post{
   719  			UserId:    th.BasicUser.Id,
   720  			ChannelId: th.BasicChannel.Id,
   721  			Message:   "message_",
   722  			CreateAt:  model.GetMillis() - 10000,
   723  		}
   724  		_, err := th.App.CreatePost(post, th.BasicChannel, false, true)
   725  		assert.Nil(t, err)
   726  		// We shutdown plugins first so that the read on the log buffer is race-free.
   727  		th.App.Srv().ShutDownPlugins()
   728  		tearDown()
   729  
   730  		testlib.AssertLog(t, th.LogBuffer, mlog.LevelDebug, "panic: some text from panic")
   731  	})
   732  }
   733  
   734  func TestProcessPrepackagedPlugins(t *testing.T) {
   735  	th := Setup(t)
   736  	defer th.TearDown()
   737  
   738  	testsPath, _ := fileutils.FindDir("tests")
   739  	prepackagedPluginsPath := filepath.Join(testsPath, prepackagedPluginsDir)
   740  	fileErr := os.Mkdir(prepackagedPluginsPath, os.ModePerm)
   741  	require.NoError(t, fileErr)
   742  	defer os.RemoveAll(prepackagedPluginsPath)
   743  
   744  	prepackagedPluginsDir, found := fileutils.FindDir(prepackagedPluginsPath)
   745  	require.True(t, found, "failed to find prepackaged plugins directory")
   746  
   747  	testPluginPath := filepath.Join(testsPath, "testplugin.tar.gz")
   748  	fileErr = testlib.CopyFile(testPluginPath, filepath.Join(prepackagedPluginsDir, "testplugin.tar.gz"))
   749  	require.NoError(t, fileErr)
   750  
   751  	t.Run("automatic, enabled plugin, no signature", func(t *testing.T) {
   752  		// Install the plugin and enable
   753  		pluginBytes, err := ioutil.ReadFile(testPluginPath)
   754  		require.NoError(t, err)
   755  		require.NotNil(t, pluginBytes)
   756  
   757  		manifest, appErr := th.App.installPluginLocally(bytes.NewReader(pluginBytes), nil, installPluginLocallyAlways)
   758  		require.Nil(t, appErr)
   759  		require.Equal(t, "testplugin", manifest.Id)
   760  
   761  		env := th.App.GetPluginsEnvironment()
   762  
   763  		activatedManifest, activated, err := env.Activate(manifest.Id)
   764  		require.NoError(t, err)
   765  		require.True(t, activated)
   766  		require.Equal(t, manifest, activatedManifest)
   767  
   768  		th.App.UpdateConfig(func(cfg *model.Config) {
   769  			*cfg.PluginSettings.Enable = true
   770  			*cfg.PluginSettings.AutomaticPrepackagedPlugins = true
   771  			*cfg.PluginSettings.EnableRemoteMarketplace = false
   772  		})
   773  
   774  		plugins := th.App.processPrepackagedPlugins(prepackagedPluginsDir)
   775  		require.Len(t, plugins, 1)
   776  		require.Equal(t, plugins[0].Manifest.Id, "testplugin")
   777  		require.Empty(t, plugins[0].Signature, 0)
   778  
   779  		pluginStatus, err := env.Statuses()
   780  		require.NoError(t, err)
   781  		require.Len(t, pluginStatus, 1)
   782  		require.Equal(t, pluginStatus[0].PluginId, "testplugin")
   783  
   784  		appErr = th.App.RemovePlugin("testplugin")
   785  		checkNoError(t, appErr)
   786  
   787  		pluginStatus, err = env.Statuses()
   788  		require.NoError(t, err)
   789  		require.Len(t, pluginStatus, 0)
   790  	})
   791  
   792  	t.Run("automatic, not enabled plugin", func(t *testing.T) {
   793  		th.App.UpdateConfig(func(cfg *model.Config) {
   794  			*cfg.PluginSettings.Enable = true
   795  			*cfg.PluginSettings.AutomaticPrepackagedPlugins = true
   796  			*cfg.PluginSettings.EnableRemoteMarketplace = false
   797  		})
   798  
   799  		env := th.App.GetPluginsEnvironment()
   800  
   801  		plugins := th.App.processPrepackagedPlugins(prepackagedPluginsDir)
   802  		require.Len(t, plugins, 1)
   803  		require.Equal(t, plugins[0].Manifest.Id, "testplugin")
   804  		require.Empty(t, plugins[0].Signature, 0)
   805  
   806  		pluginStatus, err := env.Statuses()
   807  		require.NoError(t, err)
   808  		require.Empty(t, pluginStatus, 0)
   809  	})
   810  
   811  	t.Run("automatic, multiple plugins with signatures, not enabled", func(t *testing.T) {
   812  		th.App.UpdateConfig(func(cfg *model.Config) {
   813  			*cfg.PluginSettings.Enable = true
   814  			*cfg.PluginSettings.AutomaticPrepackagedPlugins = true
   815  			*cfg.PluginSettings.EnableRemoteMarketplace = false
   816  		})
   817  
   818  		env := th.App.GetPluginsEnvironment()
   819  
   820  		// Add signature
   821  		testPluginSignaturePath := filepath.Join(testsPath, "testplugin.tar.gz.sig")
   822  		err := testlib.CopyFile(testPluginSignaturePath, filepath.Join(prepackagedPluginsDir, "testplugin.tar.gz.sig"))
   823  		require.NoError(t, err)
   824  
   825  		// Add second plugin
   826  		testPlugin2Path := filepath.Join(testsPath, "testplugin2.tar.gz")
   827  		err = testlib.CopyFile(testPlugin2Path, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz"))
   828  		require.NoError(t, err)
   829  
   830  		testPlugin2SignaturePath := filepath.Join(testsPath, "testplugin2.tar.gz.sig")
   831  		err = testlib.CopyFile(testPlugin2SignaturePath, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz.sig"))
   832  		require.NoError(t, err)
   833  
   834  		plugins := th.App.processPrepackagedPlugins(prepackagedPluginsDir)
   835  		require.Len(t, plugins, 2)
   836  		require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[0].Manifest.Id)
   837  		require.NotEmpty(t, plugins[0].Signature)
   838  		require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[1].Manifest.Id)
   839  		require.NotEmpty(t, plugins[1].Signature)
   840  
   841  		pluginStatus, err := env.Statuses()
   842  		require.NoError(t, err)
   843  		require.Len(t, pluginStatus, 0)
   844  	})
   845  
   846  	t.Run("automatic, multiple plugins with signatures, one enabled", func(t *testing.T) {
   847  		th.App.UpdateConfig(func(cfg *model.Config) {
   848  			*cfg.PluginSettings.Enable = true
   849  			*cfg.PluginSettings.AutomaticPrepackagedPlugins = true
   850  			*cfg.PluginSettings.EnableRemoteMarketplace = false
   851  		})
   852  
   853  		env := th.App.GetPluginsEnvironment()
   854  
   855  		// Add signature
   856  		testPluginSignaturePath := filepath.Join(testsPath, "testplugin.tar.gz.sig")
   857  		err := testlib.CopyFile(testPluginSignaturePath, filepath.Join(prepackagedPluginsDir, "testplugin.tar.gz.sig"))
   858  		require.NoError(t, err)
   859  
   860  		// Install first plugin and enable
   861  		pluginBytes, err := ioutil.ReadFile(testPluginPath)
   862  		require.NoError(t, err)
   863  		require.NotNil(t, pluginBytes)
   864  
   865  		manifest, appErr := th.App.installPluginLocally(bytes.NewReader(pluginBytes), nil, installPluginLocallyAlways)
   866  		require.Nil(t, appErr)
   867  		require.Equal(t, "testplugin", manifest.Id)
   868  
   869  		activatedManifest, activated, err := env.Activate(manifest.Id)
   870  		require.NoError(t, err)
   871  		require.True(t, activated)
   872  		require.Equal(t, manifest, activatedManifest)
   873  
   874  		// Add second plugin
   875  		testPlugin2Path := filepath.Join(testsPath, "testplugin2.tar.gz")
   876  		err = testlib.CopyFile(testPlugin2Path, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz"))
   877  		require.NoError(t, err)
   878  
   879  		testPlugin2SignaturePath := filepath.Join(testsPath, "testplugin2.tar.gz.sig")
   880  		err = testlib.CopyFile(testPlugin2SignaturePath, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz.sig"))
   881  		require.NoError(t, err)
   882  
   883  		plugins := th.App.processPrepackagedPlugins(prepackagedPluginsDir)
   884  		require.Len(t, plugins, 2)
   885  		require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[0].Manifest.Id)
   886  		require.NotEmpty(t, plugins[0].Signature)
   887  		require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[1].Manifest.Id)
   888  		require.NotEmpty(t, plugins[1].Signature)
   889  
   890  		pluginStatus, err := env.Statuses()
   891  		require.NoError(t, err)
   892  		require.Len(t, pluginStatus, 1)
   893  		require.Equal(t, pluginStatus[0].PluginId, "testplugin")
   894  
   895  		appErr = th.App.RemovePlugin("testplugin")
   896  		checkNoError(t, appErr)
   897  
   898  		pluginStatus, err = env.Statuses()
   899  		require.NoError(t, err)
   900  		require.Len(t, pluginStatus, 0)
   901  	})
   902  
   903  	t.Run("non-automatic, multiple plugins", func(t *testing.T) {
   904  		th.App.UpdateConfig(func(cfg *model.Config) {
   905  			*cfg.PluginSettings.Enable = true
   906  			*cfg.PluginSettings.AutomaticPrepackagedPlugins = false
   907  			*cfg.PluginSettings.EnableRemoteMarketplace = false
   908  		})
   909  
   910  		env := th.App.GetPluginsEnvironment()
   911  
   912  		testPlugin2Path := filepath.Join(testsPath, "testplugin2.tar.gz")
   913  		err := testlib.CopyFile(testPlugin2Path, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz"))
   914  		require.NoError(t, err)
   915  
   916  		testPlugin2SignaturePath := filepath.Join(testsPath, "testplugin2.tar.gz.sig")
   917  		err = testlib.CopyFile(testPlugin2SignaturePath, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz.sig"))
   918  		require.NoError(t, err)
   919  
   920  		plugins := th.App.processPrepackagedPlugins(prepackagedPluginsDir)
   921  		require.Len(t, plugins, 2)
   922  		require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[0].Manifest.Id)
   923  		require.NotEmpty(t, plugins[0].Signature)
   924  		require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[1].Manifest.Id)
   925  		require.NotEmpty(t, plugins[1].Signature)
   926  
   927  		pluginStatus, err := env.Statuses()
   928  		require.NoError(t, err)
   929  		require.Len(t, pluginStatus, 0)
   930  	})
   931  }