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 }