1// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2// See LICENSE.txt for license information. 3 4package app 5 6import ( 7 "encoding/base64" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "net/http" 12 "os" 13 "path/filepath" 14 "runtime" 15 "sort" 16 "strings" 17 "sync" 18 19 "github.com/blang/semver" 20 svg "github.com/h2non/go-is-svg" 21 "github.com/pkg/errors" 22 23 "github.com/mattermost/mattermost-server/v6/app/request" 24 "github.com/mattermost/mattermost-server/v6/model" 25 "github.com/mattermost/mattermost-server/v6/plugin" 26 "github.com/mattermost/mattermost-server/v6/services/marketplace" 27 "github.com/mattermost/mattermost-server/v6/shared/filestore" 28 "github.com/mattermost/mattermost-server/v6/shared/mlog" 29 "github.com/mattermost/mattermost-server/v6/utils/fileutils" 30) 31 32const prepackagedPluginsDir = "prepackaged_plugins" 33 34type pluginSignaturePath struct { 35 pluginID string 36 path string 37 signaturePath string 38} 39 40// GetPluginsEnvironment returns the plugin environment for use if plugins are enabled and 41// initialized. 42// 43// To get the plugins environment when the plugins are disabled, manually acquire the plugins 44// lock instead. 45func (s *Server) GetPluginsEnvironment() *plugin.Environment { 46 if !*s.Config().PluginSettings.Enable { 47 return nil 48 } 49 50 s.PluginsLock.RLock() 51 defer s.PluginsLock.RUnlock() 52 53 return s.PluginsEnvironment 54} 55 56// GetPluginsEnvironment returns the plugin environment for use if plugins are enabled and 57// initialized. 58// 59// To get the plugins environment when the plugins are disabled, manually acquire the plugins 60// lock instead. 61func (a *App) GetPluginsEnvironment() *plugin.Environment { 62 return a.Srv().GetPluginsEnvironment() 63} 64 65func (a *App) SetPluginsEnvironment(pluginsEnvironment *plugin.Environment) { 66 a.Srv().PluginsLock.Lock() 67 defer a.Srv().PluginsLock.Unlock() 68 69 a.Srv().PluginsEnvironment = pluginsEnvironment 70} 71 72func (a *App) SyncPluginsActiveState() { 73 a.Srv().syncPluginsActiveState() 74} 75 76func (s *Server) syncPluginsActiveState() { 77 // Acquiring lock manually, as plugins might be disabled. See GetPluginsEnvironment. 78 s.PluginsLock.RLock() 79 pluginsEnvironment := s.PluginsEnvironment 80 s.PluginsLock.RUnlock() 81 82 if pluginsEnvironment == nil { 83 return 84 } 85 86 config := s.Config().PluginSettings 87 88 if *config.Enable { 89 availablePlugins, err := pluginsEnvironment.Available() 90 if err != nil { 91 s.Log.Error("Unable to get available plugins", mlog.Err(err)) 92 return 93 } 94 95 // Determine which plugins need to be activated or deactivated. 96 disabledPlugins := []*model.BundleInfo{} 97 enabledPlugins := []*model.BundleInfo{} 98 for _, plugin := range availablePlugins { 99 pluginID := plugin.Manifest.Id 100 pluginEnabled := false 101 if state, ok := config.PluginStates[pluginID]; ok { 102 pluginEnabled = state.Enable 103 } 104 105 // Tie Apps proxy disabled status to the feature flag. 106 if pluginID == "com.mattermost.apps" { 107 if !s.Config().FeatureFlags.AppsEnabled { 108 pluginEnabled = false 109 } 110 } 111 112 if pluginEnabled { 113 enabledPlugins = append(enabledPlugins, plugin) 114 } else { 115 disabledPlugins = append(disabledPlugins, plugin) 116 } 117 } 118 119 // Concurrently activate/deactivate each plugin appropriately. 120 var wg sync.WaitGroup 121 122 // Deactivate any plugins that have been disabled. 123 for _, plugin := range disabledPlugins { 124 wg.Add(1) 125 go func(plugin *model.BundleInfo) { 126 defer wg.Done() 127 128 deactivated := pluginsEnvironment.Deactivate(plugin.Manifest.Id) 129 if deactivated && plugin.Manifest.HasClient() { 130 message := model.NewWebSocketEvent(model.WebsocketEventPluginDisabled, "", "", "", nil) 131 message.Add("manifest", plugin.Manifest.ClientManifest()) 132 s.Publish(message) 133 } 134 }(plugin) 135 } 136 137 // Activate any plugins that have been enabled 138 for _, plugin := range enabledPlugins { 139 wg.Add(1) 140 go func(plugin *model.BundleInfo) { 141 defer wg.Done() 142 143 pluginID := plugin.Manifest.Id 144 updatedManifest, activated, err := pluginsEnvironment.Activate(pluginID) 145 if err != nil { 146 plugin.WrapLogger(s.Log).Error("Unable to activate plugin", mlog.Err(err)) 147 return 148 } 149 150 if activated { 151 // Notify all cluster clients if ready 152 if err := s.notifyPluginEnabled(updatedManifest); err != nil { 153 s.Log.Error("Failed to notify cluster on plugin enable", mlog.Err(err)) 154 } 155 } 156 }(plugin) 157 } 158 wg.Wait() 159 } else { // If plugins are disabled, shutdown plugins. 160 pluginsEnvironment.Shutdown() 161 } 162 163 if err := s.notifyPluginStatusesChanged(); err != nil { 164 mlog.Warn("failed to notify plugin status changed", mlog.Err(err)) 165 } 166} 167 168func (a *App) NewPluginAPI(c *request.Context, manifest *model.Manifest) plugin.API { 169 return NewPluginAPI(a, c, manifest) 170} 171 172func (a *App) InitPlugins(c *request.Context, pluginDir, webappPluginDir string) { 173 a.Srv().initPlugins(c, pluginDir, webappPluginDir) 174} 175 176func (s *Server) initPlugins(c *request.Context, pluginDir, webappPluginDir string) { 177 // Acquiring lock manually, as plugins might be disabled. See GetPluginsEnvironment. 178 s.PluginsLock.RLock() 179 pluginsEnvironment := s.PluginsEnvironment 180 s.PluginsLock.RUnlock() 181 if pluginsEnvironment != nil || !*s.Config().PluginSettings.Enable { 182 s.syncPluginsActiveState() 183 return 184 } 185 186 s.Log.Info("Starting up plugins") 187 188 if err := os.Mkdir(pluginDir, 0744); err != nil && !os.IsExist(err) { 189 mlog.Error("Failed to start up plugins", mlog.Err(err)) 190 return 191 } 192 193 if err := os.Mkdir(webappPluginDir, 0744); err != nil && !os.IsExist(err) { 194 mlog.Error("Failed to start up plugins", mlog.Err(err)) 195 return 196 } 197 198 newAPIFunc := func(manifest *model.Manifest) plugin.API { 199 return New(ServerConnector(s)).NewPluginAPI(c, manifest) 200 } 201 202 env, err := plugin.NewEnvironment(newAPIFunc, NewDriverImpl(s), pluginDir, webappPluginDir, s.Log, s.Metrics) 203 if err != nil { 204 mlog.Error("Failed to start up plugins", mlog.Err(err)) 205 return 206 } 207 s.PluginsLock.Lock() 208 s.PluginsEnvironment = env 209 s.PluginsLock.Unlock() 210 211 if err := s.syncPlugins(); err != nil { 212 mlog.Error("Failed to sync plugins from the file store", mlog.Err(err)) 213 } 214 215 plugins := s.processPrepackagedPlugins(prepackagedPluginsDir) 216 pluginsEnvironment = s.GetPluginsEnvironment() 217 if pluginsEnvironment == nil { 218 mlog.Info("Plugins environment not found, server is likely shutting down") 219 return 220 } 221 pluginsEnvironment.SetPrepackagedPlugins(plugins) 222 223 s.installFeatureFlagPlugins() 224 225 // Sync plugin active state when config changes. Also notify plugins. 226 s.PluginsLock.Lock() 227 s.RemoveConfigListener(s.PluginConfigListenerId) 228 s.PluginConfigListenerId = s.AddConfigListener(func(old, new *model.Config) { 229 // If plugin status remains unchanged, only then run this. 230 // Because (*App).InitPlugins is already run as a config change hook. 231 if *old.PluginSettings.Enable == *new.PluginSettings.Enable { 232 s.installFeatureFlagPlugins() 233 s.syncPluginsActiveState() 234 } 235 if pluginsEnvironment := s.GetPluginsEnvironment(); pluginsEnvironment != nil { 236 pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 237 if err := hooks.OnConfigurationChange(); err != nil { 238 s.Log.Error("Plugin OnConfigurationChange hook failed", mlog.Err(err)) 239 } 240 return true 241 }, plugin.OnConfigurationChangeID) 242 } 243 }) 244 s.PluginsLock.Unlock() 245 246 s.syncPluginsActiveState() 247} 248 249// SyncPlugins synchronizes the plugins installed locally 250// with the plugin bundles available in the file store. 251func (a *App) SyncPlugins() *model.AppError { 252 return a.Srv().syncPlugins() 253} 254 255// SyncPlugins synchronizes the plugins installed locally 256// with the plugin bundles available in the file store. 257func (s *Server) syncPlugins() *model.AppError { 258 mlog.Info("Syncing plugins from the file store") 259 260 pluginsEnvironment := s.GetPluginsEnvironment() 261 if pluginsEnvironment == nil { 262 return model.NewAppError("SyncPlugins", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) 263 } 264 265 availablePlugins, err := pluginsEnvironment.Available() 266 if err != nil { 267 return model.NewAppError("SyncPlugins", "app.plugin.sync.read_local_folder.app_error", nil, err.Error(), http.StatusInternalServerError) 268 } 269 270 var wg sync.WaitGroup 271 for _, plugin := range availablePlugins { 272 wg.Add(1) 273 go func(pluginID string) { 274 defer wg.Done() 275 // Only handle managed plugins with .filestore flag file. 276 _, err := os.Stat(filepath.Join(*s.Config().PluginSettings.Directory, pluginID, managedPluginFileName)) 277 if os.IsNotExist(err) { 278 mlog.Warn("Skipping sync for unmanaged plugin", mlog.String("plugin_id", pluginID)) 279 } else if err != nil { 280 mlog.Error("Skipping sync for plugin after failure to check if managed", mlog.String("plugin_id", pluginID), mlog.Err(err)) 281 } else { 282 mlog.Debug("Removing local installation of managed plugin before sync", mlog.String("plugin_id", pluginID)) 283 if err := s.removePluginLocally(pluginID); err != nil { 284 mlog.Error("Failed to remove local installation of managed plugin before sync", mlog.String("plugin_id", pluginID), mlog.Err(err)) 285 } 286 } 287 }(plugin.Manifest.Id) 288 } 289 wg.Wait() 290 291 // Install plugins from the file store. 292 pluginSignaturePathMap, appErr := s.getPluginsFromFolder() 293 if appErr != nil { 294 return appErr 295 } 296 297 for _, plugin := range pluginSignaturePathMap { 298 wg.Add(1) 299 go func(plugin *pluginSignaturePath) { 300 defer wg.Done() 301 reader, appErr := s.fileReader(plugin.path) 302 if appErr != nil { 303 mlog.Error("Failed to open plugin bundle from file store.", mlog.String("bundle", plugin.path), mlog.Err(appErr)) 304 return 305 } 306 defer reader.Close() 307 308 var signature filestore.ReadCloseSeeker 309 if *s.Config().PluginSettings.RequirePluginSignature { 310 signature, appErr = s.fileReader(plugin.signaturePath) 311 if appErr != nil { 312 mlog.Error("Failed to open plugin signature from file store.", mlog.Err(appErr)) 313 return 314 } 315 defer signature.Close() 316 } 317 318 mlog.Info("Syncing plugin from file store", mlog.String("bundle", plugin.path)) 319 if _, err := s.installPluginLocally(reader, signature, installPluginLocallyAlways); err != nil { 320 mlog.Error("Failed to sync plugin from file store", mlog.String("bundle", plugin.path), mlog.Err(err)) 321 } 322 }(plugin) 323 } 324 325 wg.Wait() 326 return nil 327} 328 329func (s *Server) ShutDownPlugins() { 330 pluginsEnvironment := s.GetPluginsEnvironment() 331 if pluginsEnvironment == nil { 332 return 333 } 334 335 mlog.Info("Shutting down plugins") 336 337 pluginsEnvironment.Shutdown() 338 339 s.RemoveConfigListener(s.PluginConfigListenerId) 340 s.PluginConfigListenerId = "" 341 342 // Acquiring lock manually before cleaning up PluginsEnvironment. 343 s.PluginsLock.Lock() 344 defer s.PluginsLock.Unlock() 345 if s.PluginsEnvironment == pluginsEnvironment { 346 s.PluginsEnvironment = nil 347 } else { 348 mlog.Warn("Another PluginsEnvironment detected while shutting down plugins.") 349 } 350} 351 352func (a *App) GetActivePluginManifests() ([]*model.Manifest, *model.AppError) { 353 pluginsEnvironment := a.GetPluginsEnvironment() 354 if pluginsEnvironment == nil { 355 return nil, model.NewAppError("GetActivePluginManifests", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) 356 } 357 358 plugins := pluginsEnvironment.Active() 359 360 manifests := make([]*model.Manifest, len(plugins)) 361 for i, plugin := range plugins { 362 manifests[i] = plugin.Manifest 363 } 364 365 return manifests, nil 366} 367 368// EnablePlugin will set the config for an installed plugin to enabled, triggering asynchronous 369// activation if inactive anywhere in the cluster. 370// Notifies cluster peers through config change. 371func (a *App) EnablePlugin(id string) *model.AppError { 372 return a.Srv().enablePlugin(id) 373} 374 375func (s *Server) enablePlugin(id string) *model.AppError { 376 pluginsEnvironment := s.GetPluginsEnvironment() 377 if pluginsEnvironment == nil { 378 return model.NewAppError("EnablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) 379 } 380 381 availablePlugins, err := pluginsEnvironment.Available() 382 if err != nil { 383 return model.NewAppError("EnablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError) 384 } 385 386 id = strings.ToLower(id) 387 388 var manifest *model.Manifest 389 for _, p := range availablePlugins { 390 if p.Manifest.Id == id { 391 manifest = p.Manifest 392 break 393 } 394 } 395 396 if manifest == nil { 397 return model.NewAppError("EnablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusNotFound) 398 } 399 400 s.UpdateConfig(func(cfg *model.Config) { 401 cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: true} 402 }) 403 404 // This call will implicitly invoke SyncPluginsActiveState which will activate enabled plugins. 405 if _, _, err := s.SaveConfig(s.Config(), true); err != nil { 406 if err.Id == "ent.cluster.save_config.error" { 407 return model.NewAppError("EnablePlugin", "app.plugin.cluster.save_config.app_error", nil, "", http.StatusInternalServerError) 408 } 409 return model.NewAppError("EnablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError) 410 } 411 412 return nil 413} 414 415// DisablePlugin will set the config for an installed plugin to disabled, triggering deactivation if active. 416// Notifies cluster peers through config change. 417func (a *App) DisablePlugin(id string) *model.AppError { 418 return a.Srv().disablePlugin(id) 419} 420 421func (s *Server) disablePlugin(id string) *model.AppError { 422 pluginsEnvironment := s.GetPluginsEnvironment() 423 if pluginsEnvironment == nil { 424 return model.NewAppError("DisablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) 425 } 426 427 availablePlugins, err := pluginsEnvironment.Available() 428 if err != nil { 429 return model.NewAppError("DisablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError) 430 } 431 432 id = strings.ToLower(id) 433 434 var manifest *model.Manifest 435 for _, p := range availablePlugins { 436 if p.Manifest.Id == id { 437 manifest = p.Manifest 438 break 439 } 440 } 441 442 if manifest == nil { 443 return model.NewAppError("DisablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusNotFound) 444 } 445 446 s.UpdateConfig(func(cfg *model.Config) { 447 cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: false} 448 }) 449 s.unregisterPluginCommands(id) 450 451 // This call will implicitly invoke SyncPluginsActiveState which will deactivate disabled plugins. 452 if _, _, err := s.SaveConfig(s.Config(), true); err != nil { 453 return model.NewAppError("DisablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError) 454 } 455 456 return nil 457} 458 459func (a *App) GetPlugins() (*model.PluginsResponse, *model.AppError) { 460 pluginsEnvironment := a.GetPluginsEnvironment() 461 if pluginsEnvironment == nil { 462 return nil, model.NewAppError("GetPlugins", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) 463 } 464 465 availablePlugins, err := pluginsEnvironment.Available() 466 if err != nil { 467 return nil, model.NewAppError("GetPlugins", "app.plugin.get_plugins.app_error", nil, err.Error(), http.StatusInternalServerError) 468 } 469 resp := &model.PluginsResponse{Active: []*model.PluginInfo{}, Inactive: []*model.PluginInfo{}} 470 for _, plugin := range availablePlugins { 471 if plugin.Manifest == nil { 472 continue 473 } 474 475 info := &model.PluginInfo{ 476 Manifest: *plugin.Manifest, 477 } 478 479 if pluginsEnvironment.IsActive(plugin.Manifest.Id) { 480 resp.Active = append(resp.Active, info) 481 } else { 482 resp.Inactive = append(resp.Inactive, info) 483 } 484 } 485 486 return resp, nil 487} 488 489// GetMarketplacePlugins returns a list of plugins from the marketplace-server, 490// and plugins that are installed locally. 491func (a *App) GetMarketplacePlugins(filter *model.MarketplacePluginFilter) ([]*model.MarketplacePlugin, *model.AppError) { 492 plugins := map[string]*model.MarketplacePlugin{} 493 494 if *a.Config().PluginSettings.EnableRemoteMarketplace && !filter.LocalOnly { 495 p, appErr := a.getRemotePlugins() 496 if appErr != nil { 497 return nil, appErr 498 } 499 plugins = p 500 } 501 502 // Some plugin don't work on cloud. The remote Marketplace is aware of this fact, 503 // but prepackaged plugins are not. Hence, on a cloud installation prepackaged plugins 504 // shouldn't be shown in the Marketplace modal. 505 // This is a short term fix. The long term solution is to have a separate set of 506 // prepacked plugins for cloud: https://mattermost.atlassian.net/browse/MM-31331. 507 license := a.Srv().License() 508 if license == nil || !*license.Features.Cloud { 509 appErr := a.mergePrepackagedPlugins(plugins) 510 if appErr != nil { 511 return nil, appErr 512 } 513 } 514 515 appErr := a.mergeLocalPlugins(plugins) 516 if appErr != nil { 517 return nil, appErr 518 } 519 520 // Filter plugins. 521 var result []*model.MarketplacePlugin 522 for _, p := range plugins { 523 if pluginMatchesFilter(p.Manifest, filter.Filter) { 524 result = append(result, p) 525 } 526 } 527 528 // Sort result alphabetically. 529 sort.SliceStable(result, func(i, j int) bool { 530 return strings.ToLower(result[i].Manifest.Name) < strings.ToLower(result[j].Manifest.Name) 531 }) 532 533 return result, nil 534} 535 536// getPrepackagedPlugin returns a pre-packaged plugin. 537func (s *Server) getPrepackagedPlugin(pluginID, version string) (*plugin.PrepackagedPlugin, *model.AppError) { 538 pluginsEnvironment := s.GetPluginsEnvironment() 539 if pluginsEnvironment == nil { 540 return nil, model.NewAppError("getPrepackagedPlugin", "app.plugin.config.app_error", nil, "plugin environment is nil", http.StatusInternalServerError) 541 } 542 543 prepackagedPlugins := pluginsEnvironment.PrepackagedPlugins() 544 for _, p := range prepackagedPlugins { 545 if p.Manifest.Id == pluginID && p.Manifest.Version == version { 546 return p, nil 547 } 548 } 549 550 return nil, model.NewAppError("getPrepackagedPlugin", "app.plugin.marketplace_plugins.not_found.app_error", nil, "", http.StatusInternalServerError) 551} 552 553// getRemoteMarketplacePlugin returns plugin from marketplace-server. 554func (s *Server) getRemoteMarketplacePlugin(pluginID, version string) (*model.BaseMarketplacePlugin, *model.AppError) { 555 marketplaceClient, err := marketplace.NewClient( 556 *s.Config().PluginSettings.MarketplaceURL, 557 s.HTTPService(), 558 ) 559 if err != nil { 560 return nil, model.NewAppError("GetMarketplacePlugin", "app.plugin.marketplace_client.app_error", nil, err.Error(), http.StatusInternalServerError) 561 } 562 563 filter := s.getBaseMarketplaceFilter() 564 filter.PluginId = pluginID 565 filter.ReturnAllVersions = true 566 567 plugin, err := marketplaceClient.GetPlugin(filter, version) 568 if err != nil { 569 return nil, model.NewAppError("GetMarketplacePlugin", "app.plugin.marketplace_plugins.not_found.app_error", nil, err.Error(), http.StatusInternalServerError) 570 } 571 572 return plugin, nil 573} 574 575func (a *App) getRemotePlugins() (map[string]*model.MarketplacePlugin, *model.AppError) { 576 result := map[string]*model.MarketplacePlugin{} 577 578 pluginsEnvironment := a.GetPluginsEnvironment() 579 if pluginsEnvironment == nil { 580 return nil, model.NewAppError("getRemotePlugins", "app.plugin.config.app_error", nil, "", http.StatusInternalServerError) 581 } 582 583 marketplaceClient, err := marketplace.NewClient( 584 *a.Config().PluginSettings.MarketplaceURL, 585 a.HTTPService(), 586 ) 587 if err != nil { 588 return nil, model.NewAppError("getRemotePlugins", "app.plugin.marketplace_client.app_error", nil, err.Error(), http.StatusInternalServerError) 589 } 590 591 filter := a.getBaseMarketplaceFilter() 592 // Fetch all plugins from marketplace. 593 filter.PerPage = -1 594 595 marketplacePlugins, err := marketplaceClient.GetPlugins(filter) 596 if err != nil { 597 return nil, model.NewAppError("getRemotePlugins", "app.plugin.marketplace_client.failed_to_fetch", nil, err.Error(), http.StatusInternalServerError) 598 } 599 600 for _, p := range marketplacePlugins { 601 if p.Manifest == nil { 602 continue 603 } 604 605 result[p.Manifest.Id] = &model.MarketplacePlugin{BaseMarketplacePlugin: p} 606 } 607 608 return result, nil 609} 610 611// mergePrepackagedPlugins merges pre-packaged plugins to remote marketplace plugins list. 612func (a *App) mergePrepackagedPlugins(remoteMarketplacePlugins map[string]*model.MarketplacePlugin) *model.AppError { 613 pluginsEnvironment := a.GetPluginsEnvironment() 614 if pluginsEnvironment == nil { 615 return model.NewAppError("mergePrepackagedPlugins", "app.plugin.config.app_error", nil, "", http.StatusInternalServerError) 616 } 617 618 for _, prepackaged := range pluginsEnvironment.PrepackagedPlugins() { 619 if prepackaged.Manifest == nil { 620 continue 621 } 622 623 prepackagedMarketplace := &model.MarketplacePlugin{ 624 BaseMarketplacePlugin: &model.BaseMarketplacePlugin{ 625 HomepageURL: prepackaged.Manifest.HomepageURL, 626 IconData: prepackaged.IconData, 627 ReleaseNotesURL: prepackaged.Manifest.ReleaseNotesURL, 628 Manifest: prepackaged.Manifest, 629 }, 630 } 631 632 // If not available in marketplace, add the prepackaged 633 if remoteMarketplacePlugins[prepackaged.Manifest.Id] == nil { 634 remoteMarketplacePlugins[prepackaged.Manifest.Id] = prepackagedMarketplace 635 continue 636 } 637 638 // If available in the markteplace, only overwrite if newer. 639 prepackagedVersion, err := semver.Parse(prepackaged.Manifest.Version) 640 if err != nil { 641 return model.NewAppError("mergePrepackagedPlugins", "app.plugin.invalid_version.app_error", nil, err.Error(), http.StatusBadRequest) 642 } 643 644 marketplacePlugin := remoteMarketplacePlugins[prepackaged.Manifest.Id] 645 marketplaceVersion, err := semver.Parse(marketplacePlugin.Manifest.Version) 646 if err != nil { 647 return model.NewAppError("mergePrepackagedPlugins", "app.plugin.invalid_version.app_error", nil, err.Error(), http.StatusBadRequest) 648 } 649 650 if prepackagedVersion.GT(marketplaceVersion) { 651 remoteMarketplacePlugins[prepackaged.Manifest.Id] = prepackagedMarketplace 652 } 653 } 654 655 return nil 656} 657 658// mergeLocalPlugins merges locally installed plugins to remote marketplace plugins list. 659func (a *App) mergeLocalPlugins(remoteMarketplacePlugins map[string]*model.MarketplacePlugin) *model.AppError { 660 pluginsEnvironment := a.GetPluginsEnvironment() 661 if pluginsEnvironment == nil { 662 return model.NewAppError("GetMarketplacePlugins", "app.plugin.config.app_error", nil, "", http.StatusInternalServerError) 663 } 664 665 localPlugins, err := pluginsEnvironment.Available() 666 if err != nil { 667 return model.NewAppError("GetMarketplacePlugins", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError) 668 } 669 670 for _, plugin := range localPlugins { 671 if plugin.Manifest == nil { 672 continue 673 } 674 675 if remoteMarketplacePlugins[plugin.Manifest.Id] != nil { 676 // Remote plugin is installed. 677 remoteMarketplacePlugins[plugin.Manifest.Id].InstalledVersion = plugin.Manifest.Version 678 continue 679 } 680 681 iconData := "" 682 if plugin.Manifest.IconPath != "" { 683 iconData, err = getIcon(filepath.Join(plugin.Path, plugin.Manifest.IconPath)) 684 if err != nil { 685 mlog.Warn("Error loading local plugin icon", mlog.String("plugin", plugin.Manifest.Id), mlog.String("icon_path", plugin.Manifest.IconPath), mlog.Err(err)) 686 } 687 } 688 689 var labels []model.MarketplaceLabel 690 if *a.Config().PluginSettings.EnableRemoteMarketplace { 691 // Labels should not (yet) be localized as the labels sent by the Marketplace are not (yet) localizable. 692 labels = append(labels, model.MarketplaceLabel{ 693 Name: "Local", 694 Description: "This plugin is not listed in the marketplace", 695 }) 696 } 697 698 remoteMarketplacePlugins[plugin.Manifest.Id] = &model.MarketplacePlugin{ 699 BaseMarketplacePlugin: &model.BaseMarketplacePlugin{ 700 HomepageURL: plugin.Manifest.HomepageURL, 701 IconData: iconData, 702 ReleaseNotesURL: plugin.Manifest.ReleaseNotesURL, 703 Labels: labels, 704 Manifest: plugin.Manifest, 705 }, 706 InstalledVersion: plugin.Manifest.Version, 707 } 708 } 709 710 return nil 711} 712 713func (a *App) getBaseMarketplaceFilter() *model.MarketplacePluginFilter { 714 return a.Srv().getBaseMarketplaceFilter() 715} 716 717func (s *Server) getBaseMarketplaceFilter() *model.MarketplacePluginFilter { 718 filter := &model.MarketplacePluginFilter{ 719 ServerVersion: model.CurrentVersion, 720 } 721 722 license := s.License() 723 if license != nil && license.HasEnterpriseMarketplacePlugins() { 724 filter.EnterprisePlugins = true 725 } 726 727 if license != nil && *license.Features.Cloud { 728 filter.Cloud = true 729 } 730 731 if model.BuildEnterpriseReady == "true" { 732 filter.BuildEnterpriseReady = true 733 } 734 735 filter.Platform = runtime.GOOS + "-" + runtime.GOARCH 736 737 return filter 738} 739 740func pluginMatchesFilter(manifest *model.Manifest, filter string) bool { 741 filter = strings.TrimSpace(strings.ToLower(filter)) 742 743 if filter == "" { 744 return true 745 } 746 747 if strings.ToLower(manifest.Id) == filter { 748 return true 749 } 750 751 if strings.Contains(strings.ToLower(manifest.Name), filter) { 752 return true 753 } 754 755 if strings.Contains(strings.ToLower(manifest.Description), filter) { 756 return true 757 } 758 759 return false 760} 761 762// notifyPluginEnabled notifies connected websocket clients across all peers if the version of the given 763// plugin is same across them. 764// 765// When a peer finds itself in agreement with all other peers as to the version of the given plugin, 766// it will notify all connected websocket clients (across all peers) to trigger the (re-)installation. 767// There is a small chance that this never occurs, because the last server to finish installing dies before it can announce. 768// There is also a chance that multiple servers notify, but the webapp handles this idempotently. 769func (s *Server) notifyPluginEnabled(manifest *model.Manifest) error { 770 pluginsEnvironment := s.GetPluginsEnvironment() 771 if pluginsEnvironment == nil { 772 return errors.New("pluginsEnvironment is nil") 773 } 774 if !manifest.HasClient() || !pluginsEnvironment.IsActive(manifest.Id) { 775 return nil 776 } 777 778 var statuses model.PluginStatuses 779 780 if s.Cluster != nil { 781 var err *model.AppError 782 statuses, err = s.Cluster.GetPluginStatuses() 783 if err != nil { 784 return err 785 } 786 } 787 788 localStatus, err := s.GetPluginStatus(manifest.Id) 789 if err != nil { 790 return err 791 } 792 statuses = append(statuses, localStatus) 793 794 // This will not guard against the race condition of enabling a plugin immediately after installation. 795 // As GetPluginStatuses() will not return the new plugin (since other peers are racing to install), 796 // this peer will end up checking status against itself and will notify all webclients (including peer webclients), 797 // which may result in a 404. 798 for _, status := range statuses { 799 if status.PluginId == manifest.Id && status.Version != manifest.Version { 800 mlog.Debug("Not ready to notify webclients", mlog.String("cluster_id", status.ClusterId), mlog.String("plugin_id", manifest.Id)) 801 return nil 802 } 803 } 804 805 // Notify all cluster peer clients. 806 message := model.NewWebSocketEvent(model.WebsocketEventPluginEnabled, "", "", "", nil) 807 message.Add("manifest", manifest.ClientManifest()) 808 s.Publish(message) 809 810 return nil 811} 812 813func (s *Server) getPluginsFromFolder() (map[string]*pluginSignaturePath, *model.AppError) { 814 fileStorePaths, appErr := s.listDirectory(fileStorePluginFolder) 815 if appErr != nil { 816 return nil, model.NewAppError("getPluginsFromDir", "app.plugin.sync.list_filestore.app_error", nil, appErr.Error(), http.StatusInternalServerError) 817 } 818 819 return s.getPluginsFromFilePaths(fileStorePaths), nil 820} 821 822func (s *Server) getPluginsFromFilePaths(fileStorePaths []string) map[string]*pluginSignaturePath { 823 pluginSignaturePathMap := make(map[string]*pluginSignaturePath) 824 825 fsPrefix := "" 826 if *s.Config().FileSettings.DriverName == model.ImageDriverS3 { 827 ptr := s.Config().FileSettings.AmazonS3PathPrefix 828 if ptr != nil && *ptr != "" { 829 fsPrefix = *ptr + "/" 830 } 831 } 832 833 for _, path := range fileStorePaths { 834 path = strings.TrimPrefix(path, fsPrefix) 835 if strings.HasSuffix(path, ".tar.gz") { 836 id := strings.TrimSuffix(filepath.Base(path), ".tar.gz") 837 helper := &pluginSignaturePath{ 838 pluginID: id, 839 path: path, 840 signaturePath: "", 841 } 842 pluginSignaturePathMap[id] = helper 843 } 844 } 845 for _, path := range fileStorePaths { 846 path = strings.TrimPrefix(path, fsPrefix) 847 if strings.HasSuffix(path, ".tar.gz.sig") { 848 id := strings.TrimSuffix(filepath.Base(path), ".tar.gz.sig") 849 if val, ok := pluginSignaturePathMap[id]; !ok { 850 mlog.Warn("Unknown signature", mlog.String("path", path)) 851 } else { 852 val.signaturePath = path 853 } 854 } 855 } 856 857 return pluginSignaturePathMap 858} 859 860func (s *Server) processPrepackagedPlugins(pluginsDir string) []*plugin.PrepackagedPlugin { 861 prepackagedPluginsDir, found := fileutils.FindDir(pluginsDir) 862 if !found { 863 return nil 864 } 865 866 var fileStorePaths []string 867 err := filepath.Walk(prepackagedPluginsDir, func(walkPath string, info os.FileInfo, err error) error { 868 fileStorePaths = append(fileStorePaths, walkPath) 869 return nil 870 }) 871 if err != nil { 872 mlog.Error("Failed to walk prepackaged plugins", mlog.Err(err)) 873 return nil 874 } 875 876 pluginSignaturePathMap := s.getPluginsFromFilePaths(fileStorePaths) 877 plugins := make([]*plugin.PrepackagedPlugin, 0, len(pluginSignaturePathMap)) 878 prepackagedPlugins := make(chan *plugin.PrepackagedPlugin, len(pluginSignaturePathMap)) 879 880 var wg sync.WaitGroup 881 for _, psPath := range pluginSignaturePathMap { 882 wg.Add(1) 883 go func(psPath *pluginSignaturePath) { 884 defer wg.Done() 885 p, err := s.processPrepackagedPlugin(psPath) 886 if err != nil { 887 mlog.Error("Failed to install prepackaged plugin", mlog.String("path", psPath.path), mlog.Err(err)) 888 return 889 } 890 prepackagedPlugins <- p 891 }(psPath) 892 } 893 894 wg.Wait() 895 close(prepackagedPlugins) 896 897 for p := range prepackagedPlugins { 898 plugins = append(plugins, p) 899 } 900 901 return plugins 902} 903 904// processPrepackagedPlugin will return the prepackaged plugin metadata and will also 905// install the prepackaged plugin if it had been previously enabled and AutomaticPrepackagedPlugins is true. 906func (s *Server) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (*plugin.PrepackagedPlugin, error) { 907 mlog.Debug("Processing prepackaged plugin", mlog.String("path", pluginPath.path)) 908 909 fileReader, err := os.Open(pluginPath.path) 910 if err != nil { 911 return nil, errors.Wrapf(err, "Failed to open prepackaged plugin %s", pluginPath.path) 912 } 913 defer fileReader.Close() 914 915 tmpDir, err := ioutil.TempDir("", "plugintmp") 916 if err != nil { 917 return nil, errors.Wrap(err, "Failed to create temp dir plugintmp") 918 } 919 defer os.RemoveAll(tmpDir) 920 921 plugin, pluginDir, err := getPrepackagedPlugin(pluginPath, fileReader, tmpDir) 922 if err != nil { 923 return nil, errors.Wrapf(err, "Failed to get prepackaged plugin %s", pluginPath.path) 924 } 925 926 // Skip installing the plugin at all if automatic prepackaged plugins is disabled 927 if !*s.Config().PluginSettings.AutomaticPrepackagedPlugins { 928 return plugin, nil 929 } 930 931 // Skip installing if the plugin is has not been previously enabled. 932 pluginState := s.Config().PluginSettings.PluginStates[plugin.Manifest.Id] 933 if pluginState == nil || !pluginState.Enable { 934 return plugin, nil 935 } 936 937 mlog.Debug("Installing prepackaged plugin", mlog.String("path", pluginPath.path)) 938 if _, err := s.installExtractedPlugin(plugin.Manifest, pluginDir, installPluginLocallyOnlyIfNewOrUpgrade); err != nil { 939 return nil, errors.Wrapf(err, "Failed to install extracted prepackaged plugin %s", pluginPath.path) 940 } 941 942 return plugin, nil 943} 944 945// installFeatureFlagPlugins handles the automatic installation/upgrade of plugins from feature flags 946func (s *Server) installFeatureFlagPlugins() { 947 ffControledPlugins := s.Config().FeatureFlags.Plugins() 948 949 // Respect the automatic prepackaged disable setting 950 if !*s.Config().PluginSettings.AutomaticPrepackagedPlugins { 951 return 952 } 953 954 for pluginID, version := range ffControledPlugins { 955 // Skip installing if the plugin has been previously disabled. 956 pluginState := s.Config().PluginSettings.PluginStates[pluginID] 957 if pluginState != nil && !pluginState.Enable { 958 s.Log.Debug("Not auto installing/upgrade because plugin was disabled", mlog.String("plugin_id", pluginID), mlog.String("version", version)) 959 continue 960 } 961 962 // Check if we already installed this version as InstallMarketplacePlugin can't handle re-installs well. 963 pluginStatus, err := s.GetPluginStatus(pluginID) 964 pluginExists := err == nil 965 if pluginExists && pluginStatus.Version == version { 966 continue 967 } 968 969 if version != "" && version != "control" { 970 // If we are on-prem skip installation if this is a downgrade 971 license := s.License() 972 inCloud := license != nil && *license.Features.Cloud 973 if !inCloud && pluginExists { 974 parsedVersion, err := semver.Parse(version) 975 if err != nil { 976 s.Log.Debug("Bad version from feature flag", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) 977 return 978 } 979 parsedExistingVersion, err := semver.Parse(pluginStatus.Version) 980 if err != nil { 981 s.Log.Debug("Bad version from plugin manifest", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", pluginStatus.Version)) 982 return 983 } 984 985 if parsedVersion.LTE(parsedExistingVersion) { 986 s.Log.Debug("Skip installation because given version was a downgrade and on-prem installations should not downgrade.", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", pluginStatus.Version)) 987 return 988 } 989 } 990 991 _, err := s.installMarketplacePlugin(&model.InstallMarketplacePluginRequest{ 992 Id: pluginID, 993 Version: version, 994 }) 995 if err != nil { 996 s.Log.Debug("Unable to install plugin from FF manifest", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) 997 } else { 998 if err := s.enablePlugin(pluginID); err != nil { 999 s.Log.Debug("Unable to enable plugin installed from feature flag.", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) 1000 } else { 1001 s.Log.Debug("Installed and enabled plugin.", mlog.String("plugin_id", pluginID), mlog.String("version", version)) 1002 } 1003 } 1004 } 1005 } 1006} 1007 1008// getPrepackagedPlugin builds a PrepackagedPlugin from the plugin at the given path, additionally returning the directory in which it was extracted. 1009func getPrepackagedPlugin(pluginPath *pluginSignaturePath, pluginFile io.ReadSeeker, tmpDir string) (*plugin.PrepackagedPlugin, string, error) { 1010 manifest, pluginDir, appErr := extractPlugin(pluginFile, tmpDir) 1011 if appErr != nil { 1012 return nil, "", errors.Wrapf(appErr, "Failed to extract plugin with path %s", pluginPath.path) 1013 } 1014 1015 plugin := new(plugin.PrepackagedPlugin) 1016 plugin.Manifest = manifest 1017 plugin.Path = pluginPath.path 1018 1019 if pluginPath.signaturePath != "" { 1020 sig := pluginPath.signaturePath 1021 sigReader, sigErr := os.Open(sig) 1022 if sigErr != nil { 1023 return nil, "", errors.Wrapf(sigErr, "Failed to open prepackaged plugin signature %s", sig) 1024 } 1025 bytes, sigErr := ioutil.ReadAll(sigReader) 1026 if sigErr != nil { 1027 return nil, "", errors.Wrapf(sigErr, "Failed to read prepackaged plugin signature %s", sig) 1028 } 1029 plugin.Signature = bytes 1030 } 1031 1032 if manifest.IconPath != "" { 1033 iconData, err := getIcon(filepath.Join(pluginDir, manifest.IconPath)) 1034 if err != nil { 1035 return nil, "", errors.Wrapf(err, "Failed to read icon at %s", manifest.IconPath) 1036 } 1037 plugin.IconData = iconData 1038 } 1039 1040 return plugin, pluginDir, nil 1041} 1042 1043func getIcon(iconPath string) (string, error) { 1044 icon, err := ioutil.ReadFile(iconPath) 1045 if err != nil { 1046 return "", errors.Wrapf(err, "failed to open icon at path %s", iconPath) 1047 } 1048 1049 if !svg.Is(icon) { 1050 return "", errors.Errorf("icon is not svg %s", iconPath) 1051 } 1052 1053 return fmt.Sprintf("data:image/svg+xml;base64,%s", base64.StdEncoding.EncodeToString(icon)), nil 1054} 1055