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 }