diff options
author | Joram Wilander <jwawilander@gmail.com> | 2017-11-27 17:23:35 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-11-27 17:23:35 -0500 |
commit | 6176bcff6977bda71f4fde10a52dde6d7d7ceb9a (patch) | |
tree | b4a4a22879f4b88ffc4fb59f46ca69d441569ddd /app/plugins.go | |
parent | e85ec3830164ffdfbe8fd5696ab99446b38a01ef (diff) | |
download | chat-6176bcff6977bda71f4fde10a52dde6d7d7ceb9a.tar.gz chat-6176bcff6977bda71f4fde10a52dde6d7d7ceb9a.tar.bz2 chat-6176bcff6977bda71f4fde10a52dde6d7d7ceb9a.zip |
PLT-8131 (part2) Add plugin key value store support (#7902)
* Add plugin key value store support
* Add localization strings
* Updates per feedback
Diffstat (limited to 'app/plugins.go')
-rw-r--r-- | app/plugins.go | 649 |
1 files changed, 0 insertions, 649 deletions
diff --git a/app/plugins.go b/app/plugins.go deleted file mode 100644 index 8f81086df..000000000 --- a/app/plugins.go +++ /dev/null @@ -1,649 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package app - -import ( - "context" - "encoding/json" - "io" - "io/ioutil" - "net/http" - "os" - "path/filepath" - "strings" - - l4g "github.com/alecthomas/log4go" - - "github.com/gorilla/mux" - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" - - builtinplugin "github.com/mattermost/mattermost-server/app/plugin" - "github.com/mattermost/mattermost-server/app/plugin/jira" - "github.com/mattermost/mattermost-server/app/plugin/ldapextras" - - "github.com/mattermost/mattermost-server/plugin" - "github.com/mattermost/mattermost-server/plugin/pluginenv" -) - -type PluginAPI struct { - id string - app *App -} - -func (api *PluginAPI) LoadPluginConfiguration(dest interface{}) error { - if b, err := json.Marshal(api.app.Config().PluginSettings.Plugins[api.id]); err != nil { - return err - } else { - return json.Unmarshal(b, dest) - } -} - -func (api *PluginAPI) CreateTeam(team *model.Team) (*model.Team, *model.AppError) { - return api.app.CreateTeam(team) -} - -func (api *PluginAPI) DeleteTeam(teamId string) *model.AppError { - return api.app.SoftDeleteTeam(teamId) -} - -func (api *PluginAPI) GetTeam(teamId string) (*model.Team, *model.AppError) { - return api.app.GetTeam(teamId) -} - -func (api *PluginAPI) GetTeamByName(name string) (*model.Team, *model.AppError) { - return api.app.GetTeamByName(name) -} - -func (api *PluginAPI) UpdateTeam(team *model.Team) (*model.Team, *model.AppError) { - return api.app.UpdateTeam(team) -} - -func (api *PluginAPI) CreateUser(user *model.User) (*model.User, *model.AppError) { - return api.app.CreateUser(user) -} - -func (api *PluginAPI) DeleteUser(userId string) *model.AppError { - user, err := api.app.GetUser(userId) - if err != nil { - return err - } - _, err = api.app.UpdateActive(user, false) - return err -} - -func (api *PluginAPI) GetUser(userId string) (*model.User, *model.AppError) { - return api.app.GetUser(userId) -} - -func (api *PluginAPI) GetUserByEmail(email string) (*model.User, *model.AppError) { - return api.app.GetUserByEmail(email) -} - -func (api *PluginAPI) GetUserByUsername(name string) (*model.User, *model.AppError) { - return api.app.GetUserByUsername(name) -} - -func (api *PluginAPI) UpdateUser(user *model.User) (*model.User, *model.AppError) { - return api.app.UpdateUser(user, true) -} - -func (api *PluginAPI) CreateChannel(channel *model.Channel) (*model.Channel, *model.AppError) { - return api.app.CreateChannel(channel, false) -} - -func (api *PluginAPI) DeleteChannel(channelId string) *model.AppError { - channel, err := api.app.GetChannel(channelId) - if err != nil { - return err - } - return api.app.DeleteChannel(channel, "") -} - -func (api *PluginAPI) GetChannel(channelId string) (*model.Channel, *model.AppError) { - return api.app.GetChannel(channelId) -} - -func (api *PluginAPI) GetChannelByName(name, teamId string) (*model.Channel, *model.AppError) { - return api.app.GetChannelByName(name, teamId) -} - -func (api *PluginAPI) GetDirectChannel(userId1, userId2 string) (*model.Channel, *model.AppError) { - return api.app.GetDirectChannel(userId1, userId2) -} - -func (api *PluginAPI) GetGroupChannel(userIds []string) (*model.Channel, *model.AppError) { - return api.app.CreateGroupChannel(userIds, "") -} - -func (api *PluginAPI) UpdateChannel(channel *model.Channel) (*model.Channel, *model.AppError) { - return api.app.UpdateChannel(channel) -} - -func (api *PluginAPI) CreatePost(post *model.Post) (*model.Post, *model.AppError) { - return api.app.CreatePostMissingChannel(post, true) -} - -func (api *PluginAPI) DeletePost(postId string) *model.AppError { - _, err := api.app.DeletePost(postId) - return err -} - -func (api *PluginAPI) GetPost(postId string) (*model.Post, *model.AppError) { - return api.app.GetSinglePost(postId) -} - -func (api *PluginAPI) UpdatePost(post *model.Post) (*model.Post, *model.AppError) { - return api.app.UpdatePost(post, false) -} - -type BuiltInPluginAPI struct { - id string - router *mux.Router - app *App -} - -func (api *BuiltInPluginAPI) LoadPluginConfiguration(dest interface{}) error { - if b, err := json.Marshal(api.app.Config().PluginSettings.Plugins[api.id]); err != nil { - return err - } else { - return json.Unmarshal(b, dest) - } -} - -func (api *BuiltInPluginAPI) PluginRouter() *mux.Router { - return api.router -} - -func (api *BuiltInPluginAPI) GetTeamByName(name string) (*model.Team, *model.AppError) { - return api.app.GetTeamByName(name) -} - -func (api *BuiltInPluginAPI) GetUserByName(name string) (*model.User, *model.AppError) { - return api.app.GetUserByUsername(name) -} - -func (api *BuiltInPluginAPI) GetChannelByName(teamId, name string) (*model.Channel, *model.AppError) { - return api.app.GetChannelByName(name, teamId) -} - -func (api *BuiltInPluginAPI) GetDirectChannel(userId1, userId2 string) (*model.Channel, *model.AppError) { - return api.app.GetDirectChannel(userId1, userId2) -} - -func (api *BuiltInPluginAPI) CreatePost(post *model.Post) (*model.Post, *model.AppError) { - return api.app.CreatePostMissingChannel(post, true) -} - -func (api *BuiltInPluginAPI) GetLdapUserAttributes(userId string, attributes []string) (map[string]string, *model.AppError) { - if api.app.Ldap == nil { - return nil, model.NewAppError("GetLdapUserAttributes", "ent.ldap.disabled.app_error", nil, "", http.StatusNotImplemented) - } - - user, err := api.app.GetUser(userId) - if err != nil { - return nil, err - } - - if user.AuthData == nil { - return map[string]string{}, nil - } - - return api.app.Ldap.GetUserAttributes(*user.AuthData, attributes) -} - -func (api *BuiltInPluginAPI) GetSessionFromRequest(r *http.Request) (*model.Session, *model.AppError) { - token := "" - isTokenFromQueryString := false - - // Attempt to parse token out of the header - authHeader := r.Header.Get(model.HEADER_AUTH) - if len(authHeader) > 6 && strings.ToUpper(authHeader[0:6]) == model.HEADER_BEARER { - // Default session token - token = authHeader[7:] - - } else if len(authHeader) > 5 && strings.ToLower(authHeader[0:5]) == model.HEADER_TOKEN { - // OAuth token - token = authHeader[6:] - } - - // Attempt to parse the token from the cookie - if len(token) == 0 { - if cookie, err := r.Cookie(model.SESSION_COOKIE_TOKEN); err == nil { - token = cookie.Value - - if r.Header.Get(model.HEADER_REQUESTED_WITH) != model.HEADER_REQUESTED_WITH_XML { - return nil, model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token+" Appears to be a CSRF attempt", http.StatusUnauthorized) - } - } - } - - // Attempt to parse token out of the query string - if len(token) == 0 { - token = r.URL.Query().Get("access_token") - isTokenFromQueryString = true - } - - if len(token) == 0 { - return nil, model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token, http.StatusUnauthorized) - } - - session, err := api.app.GetSession(token) - - if err != nil { - return nil, model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token, http.StatusUnauthorized) - } else if !session.IsOAuth && isTokenFromQueryString { - return nil, model.NewAppError("ServeHTTP", "api.context.token_provided.app_error", nil, "token="+token, http.StatusUnauthorized) - } - - return session, nil -} - -func (api *BuiltInPluginAPI) I18n(id string, r *http.Request) string { - if r != nil { - f, _ := utils.GetTranslationsAndLocale(nil, r) - return f(id) - } - f, _ := utils.GetTranslationsBySystemLocale() - return f(id) -} - -func (a *App) initBuiltInPlugins() { - plugins := map[string]builtinplugin.Plugin{ - "jira": &jira.Plugin{}, - "ldapextras": &ldapextras.Plugin{}, - } - for id, p := range plugins { - l4g.Info("Initializing plugin: " + id) - api := &BuiltInPluginAPI{ - id: id, - router: a.Srv.Router.PathPrefix("/plugins/" + id).Subrouter(), - app: a, - } - p.Initialize(api) - } - utils.AddConfigListener(func(before, after *model.Config) { - for _, p := range plugins { - p.OnConfigurationChange() - } - }) - for _, p := range plugins { - p.OnConfigurationChange() - } -} - -// ActivatePlugins will activate any plugins enabled in the config -// and deactivate all other plugins. -func (a *App) ActivatePlugins() { - if a.PluginEnv == nil { - l4g.Error("plugin env not initialized") - return - } - - plugins, err := a.PluginEnv.Plugins() - if err != nil { - l4g.Error("failed to activate plugins: " + err.Error()) - return - } - - for _, plugin := range plugins { - id := plugin.Manifest.Id - - pluginState := &model.PluginState{Enable: false} - if state, ok := a.Config().PluginSettings.PluginStates[id]; ok { - pluginState = state - } - - active := a.PluginEnv.IsPluginActive(id) - - if pluginState.Enable && !active { - if err := a.PluginEnv.ActivatePlugin(id); err != nil { - l4g.Error(err.Error()) - continue - } - - if plugin.Manifest.HasClient() { - message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_ACTIVATED, "", "", "", nil) - message.Add("manifest", plugin.Manifest.ClientManifest()) - a.Publish(message) - } - - l4g.Info("Activated %v plugin", id) - } else if !pluginState.Enable && active { - if err := a.PluginEnv.DeactivatePlugin(id); err != nil { - l4g.Error(err.Error()) - continue - } - - if plugin.Manifest.HasClient() { - message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_DEACTIVATED, "", "", "", nil) - message.Add("manifest", plugin.Manifest.ClientManifest()) - a.Publish(message) - } - - l4g.Info("Deactivated %v plugin", id) - } - } -} - -// InstallPlugin unpacks and installs a plugin but does not activate it. -func (a *App) InstallPlugin(pluginFile io.Reader) (*model.Manifest, *model.AppError) { - if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable { - return nil, model.NewAppError("InstallPlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) - } - - tmpDir, err := ioutil.TempDir("", "plugintmp") - if err != nil { - return nil, model.NewAppError("InstallPlugin", "app.plugin.filesystem.app_error", nil, err.Error(), http.StatusInternalServerError) - } - defer os.RemoveAll(tmpDir) - - if err := utils.ExtractTarGz(pluginFile, tmpDir); err != nil { - return nil, model.NewAppError("InstallPlugin", "app.plugin.extract.app_error", nil, err.Error(), http.StatusBadRequest) - } - - tmpPluginDir := tmpDir - dir, err := ioutil.ReadDir(tmpDir) - if err != nil { - return nil, model.NewAppError("InstallPlugin", "app.plugin.filesystem.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - if len(dir) == 1 && dir[0].IsDir() { - tmpPluginDir = filepath.Join(tmpPluginDir, dir[0].Name()) - } - - manifest, _, err := model.FindManifest(tmpPluginDir) - if err != nil { - return nil, model.NewAppError("InstallPlugin", "app.plugin.manifest.app_error", nil, err.Error(), http.StatusBadRequest) - } - - bundles, err := a.PluginEnv.Plugins() - if err != nil { - return nil, model.NewAppError("InstallPlugin", "app.plugin.install.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - for _, bundle := range bundles { - if bundle.Manifest.Id == manifest.Id { - return nil, model.NewAppError("InstallPlugin", "app.plugin.install_id.app_error", nil, "", http.StatusBadRequest) - } - } - - err = utils.CopyDir(tmpPluginDir, filepath.Join(a.PluginEnv.SearchPath(), manifest.Id)) - if err != nil { - return nil, model.NewAppError("InstallPlugin", "app.plugin.mvdir.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - // Should add manifest validation and error handling here - - return manifest, nil -} - -func (a *App) GetPluginManifests() (*model.PluginsResponse, *model.AppError) { - if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable { - return nil, model.NewAppError("GetPluginManifests", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) - } - - plugins, err := a.PluginEnv.Plugins() - if err != nil { - return nil, model.NewAppError("GetPluginManifests", "app.plugin.get_plugins.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - resp := &model.PluginsResponse{Active: []*model.Manifest{}, Inactive: []*model.Manifest{}} - for _, plugin := range plugins { - if a.PluginEnv.IsPluginActive(plugin.Manifest.Id) { - resp.Active = append(resp.Active, plugin.Manifest) - } else { - resp.Inactive = append(resp.Inactive, plugin.Manifest) - } - } - - return resp, nil -} - -func (a *App) GetActivePluginManifests() ([]*model.Manifest, *model.AppError) { - if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable { - return nil, model.NewAppError("GetActivePluginManifests", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) - } - - plugins := a.PluginEnv.ActivePlugins() - - manifests := make([]*model.Manifest, len(plugins)) - for i, plugin := range plugins { - manifests[i] = plugin.Manifest - } - - return manifests, nil -} - -func (a *App) RemovePlugin(id string) *model.AppError { - if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable { - return model.NewAppError("RemovePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) - } - - plugins, err := a.PluginEnv.Plugins() - if err != nil { - return model.NewAppError("RemovePlugin", "app.plugin.deactivate.app_error", nil, err.Error(), http.StatusBadRequest) - } - - var manifest *model.Manifest - for _, p := range plugins { - if p.Manifest.Id == id { - manifest = p.Manifest - break - } - } - - if manifest == nil { - return model.NewAppError("RemovePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusBadRequest) - } - - if a.PluginEnv.IsPluginActive(id) { - err := a.PluginEnv.DeactivatePlugin(id) - if err != nil { - return model.NewAppError("RemovePlugin", "app.plugin.deactivate.app_error", nil, err.Error(), http.StatusBadRequest) - } - - if manifest.HasClient() { - message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_DEACTIVATED, "", "", "", nil) - message.Add("manifest", manifest.ClientManifest()) - a.Publish(message) - } - } - - err = os.RemoveAll(filepath.Join(a.PluginEnv.SearchPath(), id)) - if err != nil { - return model.NewAppError("RemovePlugin", "app.plugin.remove.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - return nil -} - -// EnablePlugin will set the config for an installed plugin to enabled, triggering activation if inactive. -func (a *App) EnablePlugin(id string) *model.AppError { - if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable { - return model.NewAppError("RemovePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) - } - - plugins, err := a.PluginEnv.Plugins() - if err != nil { - return model.NewAppError("EnablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - var manifest *model.Manifest - for _, p := range plugins { - if p.Manifest.Id == id { - manifest = p.Manifest - break - } - } - - if manifest == nil { - return model.NewAppError("EnablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusBadRequest) - } - - a.UpdateConfig(func(cfg *model.Config) { - cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: true} - }) - - if err := a.SaveConfig(a.Config(), true); err != nil { - return model.NewAppError("EnablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - return nil -} - -// DisablePlugin will set the config for an installed plugin to disabled, triggering deactivation if active. -func (a *App) DisablePlugin(id string) *model.AppError { - if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable { - return model.NewAppError("RemovePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) - } - - plugins, err := a.PluginEnv.Plugins() - if err != nil { - return model.NewAppError("DisablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - var manifest *model.Manifest - for _, p := range plugins { - if p.Manifest.Id == id { - manifest = p.Manifest - break - } - } - - if manifest == nil { - return model.NewAppError("DisablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusBadRequest) - } - - a.UpdateConfig(func(cfg *model.Config) { - cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: false} - }) - - if err := a.SaveConfig(a.Config(), true); err != nil { - return model.NewAppError("DisablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - return nil -} - -func (a *App) InitPlugins(pluginPath, webappPath string) { - if !*a.Config().PluginSettings.Enable { - return - } - - if a.PluginEnv != nil { - return - } - - l4g.Info("Starting up plugins") - - err := os.Mkdir(pluginPath, 0744) - if err != nil && !os.IsExist(err) { - l4g.Error("failed to start up plugins: " + err.Error()) - return - } - - err = os.Mkdir(webappPath, 0744) - if err != nil && !os.IsExist(err) { - l4g.Error("failed to start up plugins: " + err.Error()) - return - } - - a.PluginEnv, err = pluginenv.New( - pluginenv.SearchPath(pluginPath), - pluginenv.WebappPath(webappPath), - pluginenv.APIProvider(func(m *model.Manifest) (plugin.API, error) { - return &PluginAPI{ - id: m.Id, - app: a, - }, nil - }), - ) - - if err != nil { - l4g.Error("failed to start up plugins: " + err.Error()) - return - } - - utils.RemoveConfigListener(a.PluginConfigListenerId) - a.PluginConfigListenerId = utils.AddConfigListener(func(prevCfg, cfg *model.Config) { - if a.PluginEnv == nil { - return - } - - if *prevCfg.PluginSettings.Enable && *cfg.PluginSettings.Enable { - a.ActivatePlugins() - } - - for _, err := range a.PluginEnv.Hooks().OnConfigurationChange() { - l4g.Error(err.Error()) - } - }) - - a.ActivatePlugins() -} - -func (a *App) ServePluginRequest(w http.ResponseWriter, r *http.Request) { - if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable { - err := model.NewAppError("ServePluginRequest", "app.plugin.disabled.app_error", nil, "Enable plugins to serve plugin requests", http.StatusNotImplemented) - err.Translate(utils.T) - l4g.Error(err.Error()) - w.WriteHeader(err.StatusCode) - w.Header().Set("Content-Type", "application/json") - w.Write([]byte(err.ToJson())) - return - } - - token := "" - - authHeader := r.Header.Get(model.HEADER_AUTH) - if strings.HasPrefix(strings.ToUpper(authHeader), model.HEADER_BEARER+":") { - token = authHeader[len(model.HEADER_BEARER)+1:] - } else if strings.HasPrefix(strings.ToLower(authHeader), model.HEADER_TOKEN+":") { - token = authHeader[len(model.HEADER_TOKEN)+1:] - } 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) { - token = cookie.Value - } else { - token = r.URL.Query().Get("access_token") - } - - r.Header.Del("Mattermost-User-Id") - if token != "" { - if session, err := a.GetSession(token); err != nil { - r.Header.Set("Mattermost-User-Id", session.UserId) - } - } - - cookies := r.Cookies() - r.Header.Del("Cookie") - for _, c := range cookies { - if c.Name != model.SESSION_COOKIE_TOKEN { - r.AddCookie(c) - } - } - r.Header.Del(model.HEADER_AUTH) - r.Header.Del("Referer") - - newQuery := r.URL.Query() - newQuery.Del("access_token") - r.URL.RawQuery = newQuery.Encode() - - params := mux.Vars(r) - a.PluginEnv.Hooks().ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), "plugin_id", params["plugin_id"]))) -} - -func (a *App) ShutDownPlugins() { - if a.PluginEnv == nil { - return - } - - l4g.Info("Shutting down plugins") - - for _, err := range a.PluginEnv.Shutdown() { - l4g.Error(err.Error()) - } - utils.RemoveConfigListener(a.PluginConfigListenerId) - a.PluginConfigListenerId = "" - a.PluginEnv = nil -} |