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