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