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