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