github.com/spline-fu/mattermost-server@v4.10.10+incompatible/app/plugin.go (about)

     1  // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"crypto/sha256"
    10  	"encoding/base64"
    11  	"fmt"
    12  	"io"
    13  	"io/ioutil"
    14  	"net/http"
    15  	"os"
    16  	"path/filepath"
    17  	"strings"
    18  
    19  	"github.com/gorilla/mux"
    20  	"github.com/mattermost/mattermost-server/mlog"
    21  	"github.com/mattermost/mattermost-server/model"
    22  	"github.com/mattermost/mattermost-server/utils"
    23  
    24  	builtinplugin "github.com/mattermost/mattermost-server/app/plugin"
    25  	"github.com/mattermost/mattermost-server/app/plugin/jira"
    26  	"github.com/mattermost/mattermost-server/app/plugin/ldapextras"
    27  	"github.com/mattermost/mattermost-server/app/plugin/zoom"
    28  
    29  	"github.com/mattermost/mattermost-server/plugin"
    30  	"github.com/mattermost/mattermost-server/plugin/pluginenv"
    31  	"github.com/mattermost/mattermost-server/plugin/rpcplugin"
    32  	"github.com/mattermost/mattermost-server/plugin/rpcplugin/sandbox"
    33  )
    34  
    35  var prepackagedPlugins map[string]func(string) ([]byte, error) = map[string]func(string) ([]byte, error){
    36  	"jira": jira.Asset,
    37  	"zoom": zoom.Asset,
    38  }
    39  
    40  func (a *App) initBuiltInPlugins() {
    41  	plugins := map[string]builtinplugin.Plugin{
    42  		"ldapextras": &ldapextras.Plugin{},
    43  	}
    44  	for id, p := range plugins {
    45  		mlog.Debug("Initializing built-in plugin", mlog.String("plugin_id", id))
    46  		api := &BuiltInPluginAPI{
    47  			id:     id,
    48  			router: a.Srv.Router.PathPrefix("/plugins/" + id).Subrouter(),
    49  			app:    a,
    50  		}
    51  		p.Initialize(api)
    52  	}
    53  	a.AddConfigListener(func(before, after *model.Config) {
    54  		for _, p := range plugins {
    55  			p.OnConfigurationChange()
    56  		}
    57  	})
    58  	for _, p := range plugins {
    59  		p.OnConfigurationChange()
    60  	}
    61  }
    62  
    63  func (a *App) setPluginsActive(activate bool) {
    64  	if a.PluginEnv == nil {
    65  		mlog.Error(fmt.Sprintf("Cannot setPluginsActive(%t): plugin env not initialized", activate))
    66  		return
    67  	}
    68  
    69  	plugins, err := a.PluginEnv.Plugins()
    70  	if err != nil {
    71  		mlog.Error(fmt.Sprintf("Cannot setPluginsActive(%t)", activate), mlog.Err(err))
    72  		return
    73  	}
    74  
    75  	for _, plugin := range plugins {
    76  		if plugin.Manifest == nil {
    77  			continue
    78  		}
    79  
    80  		id := plugin.Manifest.Id
    81  
    82  		pluginState := &model.PluginState{Enable: false}
    83  		if state, ok := a.Config().PluginSettings.PluginStates[id]; ok {
    84  			pluginState = state
    85  		}
    86  
    87  		active := a.PluginEnv.IsPluginActive(id)
    88  
    89  		if activate && pluginState.Enable && !active {
    90  			if err := a.activatePlugin(plugin.Manifest); err != nil {
    91  				mlog.Error("Plugin failed to activate", mlog.String("plugin_id", plugin.Manifest.Id), mlog.String("err", err.DetailedError))
    92  			}
    93  
    94  		} else if (!activate || !pluginState.Enable) && active {
    95  			if err := a.deactivatePlugin(plugin.Manifest); err != nil {
    96  				mlog.Error("Plugin failed to deactivate", mlog.String("plugin_id", plugin.Manifest.Id), mlog.String("err", err.DetailedError))
    97  			}
    98  		}
    99  	}
   100  }
   101  
   102  func (a *App) activatePlugin(manifest *model.Manifest) *model.AppError {
   103  	if err := a.PluginEnv.ActivatePlugin(manifest.Id); err != nil {
   104  		return model.NewAppError("activatePlugin", "app.plugin.activate.app_error", nil, err.Error(), http.StatusBadRequest)
   105  	}
   106  
   107  	if manifest.HasClient() {
   108  		message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_ACTIVATED, "", "", "", nil)
   109  		message.Add("manifest", manifest.ClientManifest())
   110  		a.Publish(message)
   111  	}
   112  
   113  	mlog.Info("Activated plugin", mlog.String("plugin_id", manifest.Id))
   114  	return nil
   115  }
   116  
   117  func (a *App) deactivatePlugin(manifest *model.Manifest) *model.AppError {
   118  	if err := a.PluginEnv.DeactivatePlugin(manifest.Id); err != nil {
   119  		return model.NewAppError("deactivatePlugin", "app.plugin.deactivate.app_error", nil, err.Error(), http.StatusBadRequest)
   120  	}
   121  
   122  	a.UnregisterPluginCommands(manifest.Id)
   123  
   124  	if manifest.HasClient() {
   125  		message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_DEACTIVATED, "", "", "", nil)
   126  		message.Add("manifest", manifest.ClientManifest())
   127  		a.Publish(message)
   128  	}
   129  
   130  	mlog.Info("Deactivated plugin", mlog.String("plugin_id", manifest.Id))
   131  	return nil
   132  }
   133  
   134  // InstallPlugin unpacks and installs a plugin but does not activate it.
   135  func (a *App) InstallPlugin(pluginFile io.Reader) (*model.Manifest, *model.AppError) {
   136  	return a.installPlugin(pluginFile, false)
   137  }
   138  
   139  func (a *App) installPlugin(pluginFile io.Reader, allowPrepackaged bool) (*model.Manifest, *model.AppError) {
   140  	if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable {
   141  		return nil, model.NewAppError("installPlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
   142  	}
   143  
   144  	tmpDir, err := ioutil.TempDir("", "plugintmp")
   145  	if err != nil {
   146  		return nil, model.NewAppError("installPlugin", "app.plugin.filesystem.app_error", nil, err.Error(), http.StatusInternalServerError)
   147  	}
   148  	defer os.RemoveAll(tmpDir)
   149  
   150  	if err := utils.ExtractTarGz(pluginFile, tmpDir); err != nil {
   151  		return nil, model.NewAppError("installPlugin", "app.plugin.extract.app_error", nil, err.Error(), http.StatusBadRequest)
   152  	}
   153  
   154  	tmpPluginDir := tmpDir
   155  	dir, err := ioutil.ReadDir(tmpDir)
   156  	if err != nil {
   157  		return nil, model.NewAppError("installPlugin", "app.plugin.filesystem.app_error", nil, err.Error(), http.StatusInternalServerError)
   158  	}
   159  
   160  	if len(dir) == 1 && dir[0].IsDir() {
   161  		tmpPluginDir = filepath.Join(tmpPluginDir, dir[0].Name())
   162  	}
   163  
   164  	manifest, _, err := model.FindManifest(tmpPluginDir)
   165  	if err != nil {
   166  		return nil, model.NewAppError("installPlugin", "app.plugin.manifest.app_error", nil, err.Error(), http.StatusBadRequest)
   167  	}
   168  
   169  	if _, ok := prepackagedPlugins[manifest.Id]; ok && !allowPrepackaged {
   170  		return nil, model.NewAppError("installPlugin", "app.plugin.prepackaged.app_error", nil, "", http.StatusBadRequest)
   171  	}
   172  
   173  	if !plugin.IsValidId(manifest.Id) {
   174  		return nil, model.NewAppError("installPlugin", "app.plugin.invalid_id.app_error", map[string]interface{}{"Min": plugin.MinIdLength, "Max": plugin.MaxIdLength, "Regex": plugin.ValidId.String()}, "", http.StatusBadRequest)
   175  	}
   176  
   177  	bundles, err := a.PluginEnv.Plugins()
   178  	if err != nil {
   179  		return nil, model.NewAppError("installPlugin", "app.plugin.install.app_error", nil, err.Error(), http.StatusInternalServerError)
   180  	}
   181  
   182  	for _, bundle := range bundles {
   183  		if bundle.Manifest != nil && bundle.Manifest.Id == manifest.Id {
   184  			return nil, model.NewAppError("installPlugin", "app.plugin.install_id.app_error", nil, "", http.StatusBadRequest)
   185  		}
   186  	}
   187  
   188  	err = utils.CopyDir(tmpPluginDir, filepath.Join(a.PluginEnv.SearchPath(), manifest.Id))
   189  	if err != nil {
   190  		return nil, model.NewAppError("installPlugin", "app.plugin.mvdir.app_error", nil, err.Error(), http.StatusInternalServerError)
   191  	}
   192  
   193  	// Should add manifest validation and error handling here
   194  
   195  	return manifest, nil
   196  }
   197  
   198  func (a *App) GetPlugins() (*model.PluginsResponse, *model.AppError) {
   199  	if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable {
   200  		return nil, model.NewAppError("GetPlugins", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
   201  	}
   202  
   203  	plugins, err := a.PluginEnv.Plugins()
   204  	if err != nil {
   205  		return nil, model.NewAppError("GetPlugins", "app.plugin.get_plugins.app_error", nil, err.Error(), http.StatusInternalServerError)
   206  	}
   207  
   208  	resp := &model.PluginsResponse{Active: []*model.PluginInfo{}, Inactive: []*model.PluginInfo{}}
   209  	for _, plugin := range plugins {
   210  		if plugin.Manifest == nil {
   211  			continue
   212  		}
   213  
   214  		info := &model.PluginInfo{
   215  			Manifest: *plugin.Manifest,
   216  		}
   217  		_, info.Prepackaged = prepackagedPlugins[plugin.Manifest.Id]
   218  		if a.PluginEnv.IsPluginActive(plugin.Manifest.Id) {
   219  			resp.Active = append(resp.Active, info)
   220  		} else {
   221  			resp.Inactive = append(resp.Inactive, info)
   222  		}
   223  	}
   224  
   225  	return resp, nil
   226  }
   227  
   228  func (a *App) GetActivePluginManifests() ([]*model.Manifest, *model.AppError) {
   229  	if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable {
   230  		return nil, model.NewAppError("GetActivePluginManifests", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
   231  	}
   232  
   233  	plugins := a.PluginEnv.ActivePlugins()
   234  
   235  	manifests := make([]*model.Manifest, len(plugins))
   236  	for i, plugin := range plugins {
   237  		manifests[i] = plugin.Manifest
   238  	}
   239  
   240  	return manifests, nil
   241  }
   242  
   243  func (a *App) RemovePlugin(id string) *model.AppError {
   244  	return a.removePlugin(id, false)
   245  }
   246  
   247  func (a *App) removePlugin(id string, allowPrepackaged bool) *model.AppError {
   248  	if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable {
   249  		return model.NewAppError("removePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
   250  	}
   251  
   252  	if _, ok := prepackagedPlugins[id]; ok && !allowPrepackaged {
   253  		return model.NewAppError("removePlugin", "app.plugin.prepackaged.app_error", nil, "", http.StatusBadRequest)
   254  	}
   255  
   256  	plugins, err := a.PluginEnv.Plugins()
   257  	if err != nil {
   258  		return model.NewAppError("removePlugin", "app.plugin.deactivate.app_error", nil, err.Error(), http.StatusBadRequest)
   259  	}
   260  
   261  	var manifest *model.Manifest
   262  	var pluginPath string
   263  	for _, p := range plugins {
   264  		if p.Manifest != nil && p.Manifest.Id == id {
   265  			manifest = p.Manifest
   266  			pluginPath = filepath.Dir(p.ManifestPath)
   267  			break
   268  		}
   269  	}
   270  
   271  	if manifest == nil {
   272  		return model.NewAppError("removePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusBadRequest)
   273  	}
   274  
   275  	if a.PluginEnv.IsPluginActive(id) {
   276  		err := a.deactivatePlugin(manifest)
   277  		if err != nil {
   278  			return err
   279  		}
   280  	}
   281  
   282  	err = os.RemoveAll(pluginPath)
   283  	if err != nil {
   284  		return model.NewAppError("removePlugin", "app.plugin.remove.app_error", nil, err.Error(), http.StatusInternalServerError)
   285  	}
   286  
   287  	return nil
   288  }
   289  
   290  // EnablePlugin will set the config for an installed plugin to enabled, triggering activation if inactive.
   291  func (a *App) EnablePlugin(id string) *model.AppError {
   292  	if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable {
   293  		return model.NewAppError("EnablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
   294  	}
   295  
   296  	plugins, err := a.PluginEnv.Plugins()
   297  	if err != nil {
   298  		return model.NewAppError("EnablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError)
   299  	}
   300  
   301  	var manifest *model.Manifest
   302  	for _, p := range plugins {
   303  		if p.Manifest.Id == id {
   304  			manifest = p.Manifest
   305  			break
   306  		}
   307  	}
   308  
   309  	if manifest == nil {
   310  		return model.NewAppError("EnablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusBadRequest)
   311  	}
   312  
   313  	if err := a.activatePlugin(manifest); err != nil {
   314  		return err
   315  	}
   316  
   317  	a.UpdateConfig(func(cfg *model.Config) {
   318  		cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: true}
   319  	})
   320  
   321  	if err := a.SaveConfig(a.Config(), true); err != nil {
   322  		if err.Id == "ent.cluster.save_config.error" {
   323  			return model.NewAppError("EnablePlugin", "app.plugin.cluster.save_config.app_error", nil, "", http.StatusInternalServerError)
   324  		}
   325  		return model.NewAppError("EnablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError)
   326  	}
   327  
   328  	return nil
   329  }
   330  
   331  // DisablePlugin will set the config for an installed plugin to disabled, triggering deactivation if active.
   332  func (a *App) DisablePlugin(id string) *model.AppError {
   333  	if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable {
   334  		return model.NewAppError("DisablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
   335  	}
   336  
   337  	plugins, err := a.PluginEnv.Plugins()
   338  	if err != nil {
   339  		return model.NewAppError("DisablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError)
   340  	}
   341  
   342  	var manifest *model.Manifest
   343  	for _, p := range plugins {
   344  		if p.Manifest.Id == id {
   345  			manifest = p.Manifest
   346  			break
   347  		}
   348  	}
   349  
   350  	if manifest == nil {
   351  		return model.NewAppError("DisablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusBadRequest)
   352  	}
   353  
   354  	a.UpdateConfig(func(cfg *model.Config) {
   355  		cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: false}
   356  	})
   357  
   358  	if err := a.SaveConfig(a.Config(), true); err != nil {
   359  		return model.NewAppError("DisablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError)
   360  	}
   361  
   362  	return nil
   363  }
   364  
   365  func (a *App) InitPlugins(pluginPath, webappPath string, supervisorOverride pluginenv.SupervisorProviderFunc) {
   366  	if !*a.Config().PluginSettings.Enable {
   367  		return
   368  	}
   369  
   370  	if a.PluginEnv != nil {
   371  		return
   372  	}
   373  
   374  	mlog.Info("Starting up plugins")
   375  
   376  	if err := os.Mkdir(pluginPath, 0744); err != nil && !os.IsExist(err) {
   377  		mlog.Error("Failed to start up plugins", mlog.Err(err))
   378  		return
   379  	}
   380  
   381  	if err := os.Mkdir(webappPath, 0744); err != nil && !os.IsExist(err) {
   382  		mlog.Error("Failed to start up plugins", mlog.Err(err))
   383  		return
   384  	}
   385  
   386  	options := []pluginenv.Option{
   387  		pluginenv.SearchPath(pluginPath),
   388  		pluginenv.WebappPath(webappPath),
   389  		pluginenv.APIProvider(func(m *model.Manifest) (plugin.API, error) {
   390  			return &PluginAPI{
   391  				id:  m.Id,
   392  				app: a,
   393  				keyValueStore: &PluginKeyValueStore{
   394  					id:  m.Id,
   395  					app: a,
   396  				},
   397  			}, nil
   398  		}),
   399  	}
   400  
   401  	if supervisorOverride != nil {
   402  		options = append(options, pluginenv.SupervisorProvider(supervisorOverride))
   403  	} else if err := sandbox.CheckSupport(); err != nil {
   404  		mlog.Warn("plugin sandboxing is not supported. plugins will run with the same access level as the server. See documentation to learn more: https://developers.mattermost.com/extend/plugins/security/", mlog.Err(err))
   405  		options = append(options, pluginenv.SupervisorProvider(rpcplugin.SupervisorProvider))
   406  	} else {
   407  		options = append(options, pluginenv.SupervisorProvider(sandbox.SupervisorProvider))
   408  	}
   409  
   410  	if env, err := pluginenv.New(options...); err != nil {
   411  		mlog.Error("Failed to start up plugins", mlog.Err(err))
   412  		return
   413  	} else {
   414  		a.PluginEnv = env
   415  	}
   416  
   417  	for id, asset := range prepackagedPlugins {
   418  		if tarball, err := asset("plugin.tar.gz"); err != nil {
   419  			mlog.Error("Failed to install prepackaged plugin", mlog.Err(err))
   420  		} else if tarball != nil {
   421  			a.removePlugin(id, true)
   422  			if _, err := a.installPlugin(bytes.NewReader(tarball), true); err != nil {
   423  				mlog.Error("Failed to install prepackaged plugin", mlog.Err(err))
   424  			}
   425  			if _, ok := a.Config().PluginSettings.PluginStates[id]; !ok && id != "zoom" {
   426  				if err := a.EnablePlugin(id); err != nil {
   427  					mlog.Error("Failed to enable prepackaged plugin", mlog.Err(err))
   428  				}
   429  			}
   430  		}
   431  	}
   432  
   433  	a.RemoveConfigListener(a.PluginConfigListenerId)
   434  	a.PluginConfigListenerId = a.AddConfigListener(func(_, cfg *model.Config) {
   435  		if a.PluginEnv == nil {
   436  			return
   437  		}
   438  
   439  		a.setPluginsActive(*cfg.PluginSettings.Enable)
   440  
   441  		for _, err := range a.PluginEnv.Hooks().OnConfigurationChange() {
   442  			mlog.Error(err.Error())
   443  		}
   444  	})
   445  
   446  	a.setPluginsActive(true)
   447  }
   448  
   449  func (a *App) ServePluginRequest(w http.ResponseWriter, r *http.Request) {
   450  	if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable {
   451  		err := model.NewAppError("ServePluginRequest", "app.plugin.disabled.app_error", nil, "Enable plugins to serve plugin requests", http.StatusNotImplemented)
   452  		mlog.Error(err.Error())
   453  		w.WriteHeader(err.StatusCode)
   454  		w.Header().Set("Content-Type", "application/json")
   455  		w.Write([]byte(err.ToJson()))
   456  		return
   457  	}
   458  
   459  	a.servePluginRequest(w, r, a.PluginEnv.Hooks().ServeHTTP)
   460  }
   461  
   462  func (a *App) servePluginRequest(w http.ResponseWriter, r *http.Request, handler http.HandlerFunc) {
   463  	token := ""
   464  
   465  	authHeader := r.Header.Get(model.HEADER_AUTH)
   466  	if strings.HasPrefix(strings.ToUpper(authHeader), model.HEADER_BEARER+" ") {
   467  		token = authHeader[len(model.HEADER_BEARER)+1:]
   468  	} else if strings.HasPrefix(strings.ToLower(authHeader), model.HEADER_TOKEN+" ") {
   469  		token = authHeader[len(model.HEADER_TOKEN)+1:]
   470  	} else if cookie, _ := r.Cookie(model.SESSION_COOKIE_TOKEN); cookie != nil && (r.Method == "GET" || r.Header.Get(model.HEADER_REQUESTED_WITH) == model.HEADER_REQUESTED_WITH_XML) {
   471  		token = cookie.Value
   472  	} else {
   473  		token = r.URL.Query().Get("access_token")
   474  	}
   475  
   476  	r.Header.Del("Mattermost-User-Id")
   477  	if token != "" {
   478  		if session, err := a.GetSession(token); session != nil && err == nil {
   479  			r.Header.Set("Mattermost-User-Id", session.UserId)
   480  		}
   481  	}
   482  
   483  	cookies := r.Cookies()
   484  	r.Header.Del("Cookie")
   485  	for _, c := range cookies {
   486  		if c.Name != model.SESSION_COOKIE_TOKEN {
   487  			r.AddCookie(c)
   488  		}
   489  	}
   490  	r.Header.Del(model.HEADER_AUTH)
   491  	r.Header.Del("Referer")
   492  
   493  	params := mux.Vars(r)
   494  
   495  	newQuery := r.URL.Query()
   496  	newQuery.Del("access_token")
   497  	r.URL.RawQuery = newQuery.Encode()
   498  	r.URL.Path = strings.TrimPrefix(r.URL.Path, "/plugins/"+params["plugin_id"])
   499  
   500  	handler(w, r.WithContext(context.WithValue(r.Context(), "plugin_id", params["plugin_id"])))
   501  }
   502  
   503  func (a *App) ShutDownPlugins() {
   504  	if a.PluginEnv == nil {
   505  		return
   506  	}
   507  
   508  	mlog.Info("Shutting down plugins")
   509  
   510  	for _, err := range a.PluginEnv.Shutdown() {
   511  		mlog.Error(err.Error())
   512  	}
   513  	a.RemoveConfigListener(a.PluginConfigListenerId)
   514  	a.PluginConfigListenerId = ""
   515  	a.PluginEnv = nil
   516  }
   517  
   518  func getKeyHash(key string) string {
   519  	hash := sha256.New()
   520  	hash.Write([]byte(key))
   521  	return base64.StdEncoding.EncodeToString(hash.Sum(nil))
   522  }
   523  
   524  func (a *App) SetPluginKey(pluginId string, key string, value []byte) *model.AppError {
   525  	kv := &model.PluginKeyValue{
   526  		PluginId: pluginId,
   527  		Key:      getKeyHash(key),
   528  		Value:    value,
   529  	}
   530  
   531  	result := <-a.Srv.Store.Plugin().SaveOrUpdate(kv)
   532  
   533  	if result.Err != nil {
   534  		mlog.Error(result.Err.Error())
   535  	}
   536  
   537  	return result.Err
   538  }
   539  
   540  func (a *App) GetPluginKey(pluginId string, key string) ([]byte, *model.AppError) {
   541  	result := <-a.Srv.Store.Plugin().Get(pluginId, getKeyHash(key))
   542  
   543  	if result.Err != nil {
   544  		if result.Err.StatusCode == http.StatusNotFound {
   545  			return nil, nil
   546  		}
   547  		mlog.Error(result.Err.Error())
   548  		return nil, result.Err
   549  	}
   550  
   551  	kv := result.Data.(*model.PluginKeyValue)
   552  
   553  	return kv.Value, nil
   554  }
   555  
   556  func (a *App) DeletePluginKey(pluginId string, key string) *model.AppError {
   557  	result := <-a.Srv.Store.Plugin().Delete(pluginId, getKeyHash(key))
   558  
   559  	if result.Err != nil {
   560  		mlog.Error(result.Err.Error())
   561  	}
   562  
   563  	return result.Err
   564  }
   565  
   566  type PluginCommand struct {
   567  	Command  *model.Command
   568  	PluginId string
   569  }
   570  
   571  func (a *App) RegisterPluginCommand(pluginId string, command *model.Command) error {
   572  	if command.Trigger == "" {
   573  		return fmt.Errorf("invalid command")
   574  	}
   575  
   576  	command = &model.Command{
   577  		Trigger:          strings.ToLower(command.Trigger),
   578  		TeamId:           command.TeamId,
   579  		AutoComplete:     command.AutoComplete,
   580  		AutoCompleteDesc: command.AutoCompleteDesc,
   581  		AutoCompleteHint: command.AutoCompleteHint,
   582  		DisplayName:      command.DisplayName,
   583  	}
   584  
   585  	a.pluginCommandsLock.Lock()
   586  	defer a.pluginCommandsLock.Unlock()
   587  
   588  	for _, pc := range a.pluginCommands {
   589  		if pc.Command.Trigger == command.Trigger && pc.Command.TeamId == command.TeamId {
   590  			if pc.PluginId == pluginId {
   591  				pc.Command = command
   592  				return nil
   593  			}
   594  		}
   595  	}
   596  
   597  	a.pluginCommands = append(a.pluginCommands, &PluginCommand{
   598  		Command:  command,
   599  		PluginId: pluginId,
   600  	})
   601  	return nil
   602  }
   603  
   604  func (a *App) UnregisterPluginCommand(pluginId, teamId, trigger string) {
   605  	trigger = strings.ToLower(trigger)
   606  
   607  	a.pluginCommandsLock.Lock()
   608  	defer a.pluginCommandsLock.Unlock()
   609  
   610  	var remaining []*PluginCommand
   611  	for _, pc := range a.pluginCommands {
   612  		if pc.Command.TeamId != teamId || pc.Command.Trigger != trigger {
   613  			remaining = append(remaining, pc)
   614  		}
   615  	}
   616  	a.pluginCommands = remaining
   617  }
   618  
   619  func (a *App) UnregisterPluginCommands(pluginId string) {
   620  	a.pluginCommandsLock.Lock()
   621  	defer a.pluginCommandsLock.Unlock()
   622  
   623  	var remaining []*PluginCommand
   624  	for _, pc := range a.pluginCommands {
   625  		if pc.PluginId != pluginId {
   626  			remaining = append(remaining, pc)
   627  		}
   628  	}
   629  	a.pluginCommands = remaining
   630  }
   631  
   632  func (a *App) PluginCommandsForTeam(teamId string) []*model.Command {
   633  	a.pluginCommandsLock.RLock()
   634  	defer a.pluginCommandsLock.RUnlock()
   635  
   636  	var commands []*model.Command
   637  	for _, pc := range a.pluginCommands {
   638  		if pc.Command.TeamId == "" || pc.Command.TeamId == teamId {
   639  			commands = append(commands, pc.Command)
   640  		}
   641  	}
   642  	return commands
   643  }
   644  
   645  func (a *App) ExecutePluginCommand(args *model.CommandArgs) (*model.Command, *model.CommandResponse, *model.AppError) {
   646  	parts := strings.Split(args.Command, " ")
   647  	trigger := parts[0][1:]
   648  	trigger = strings.ToLower(trigger)
   649  
   650  	a.pluginCommandsLock.RLock()
   651  	defer a.pluginCommandsLock.RUnlock()
   652  
   653  	for _, pc := range a.pluginCommands {
   654  		if (pc.Command.TeamId == "" || pc.Command.TeamId == args.TeamId) && pc.Command.Trigger == trigger {
   655  			response, appErr, err := a.PluginEnv.HooksForPlugin(pc.PluginId).ExecuteCommand(args)
   656  			if err != nil {
   657  				return pc.Command, nil, model.NewAppError("ExecutePluginCommand", "model.plugin_command.error.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
   658  			}
   659  			return pc.Command, response, appErr
   660  		}
   661  	}
   662  	return nil, nil, nil
   663  }