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 }