2017-08-02 01:36:54 -07:00
|
|
|
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
|
|
|
|
// See License.txt for license information.
|
|
|
|
|
|
|
|
|
|
package app
|
|
|
|
|
|
|
|
|
|
import (
|
2017-11-30 14:55:44 -06:00
|
|
|
"bytes"
|
2017-09-11 10:02:02 -05:00
|
|
|
"context"
|
2017-12-05 18:19:33 -05:00
|
|
|
"crypto/sha256"
|
|
|
|
|
"encoding/base64"
|
2017-12-08 13:55:41 -06:00
|
|
|
"fmt"
|
2017-09-01 09:00:27 -04:00
|
|
|
"io"
|
|
|
|
|
"io/ioutil"
|
2017-08-02 01:36:54 -07:00
|
|
|
"net/http"
|
2017-09-01 09:00:27 -04:00
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
2017-08-02 01:36:54 -07:00
|
|
|
|
|
|
|
|
"github.com/gorilla/mux"
|
2018-04-27 12:49:45 -07:00
|
|
|
"github.com/mattermost/mattermost-server/mlog"
|
2017-09-06 23:05:10 -07:00
|
|
|
"github.com/mattermost/mattermost-server/model"
|
|
|
|
|
"github.com/mattermost/mattermost-server/utils"
|
2017-08-02 01:36:54 -07:00
|
|
|
|
2017-09-11 10:02:02 -05:00
|
|
|
builtinplugin "github.com/mattermost/mattermost-server/app/plugin"
|
2017-09-06 23:05:10 -07:00
|
|
|
"github.com/mattermost/mattermost-server/app/plugin/jira"
|
|
|
|
|
"github.com/mattermost/mattermost-server/app/plugin/ldapextras"
|
2017-12-05 16:35:46 -05:00
|
|
|
"github.com/mattermost/mattermost-server/app/plugin/zoom"
|
2017-09-11 10:02:02 -05:00
|
|
|
|
|
|
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
|
|
|
"github.com/mattermost/mattermost-server/plugin/pluginenv"
|
2018-01-15 11:21:06 -06:00
|
|
|
"github.com/mattermost/mattermost-server/plugin/rpcplugin"
|
|
|
|
|
"github.com/mattermost/mattermost-server/plugin/rpcplugin/sandbox"
|
2017-08-02 01:36:54 -07:00
|
|
|
)
|
|
|
|
|
|
2017-11-30 14:55:44 -06:00
|
|
|
var prepackagedPlugins map[string]func(string) ([]byte, error) = map[string]func(string) ([]byte, error){
|
|
|
|
|
"jira": jira.Asset,
|
2017-12-05 16:35:46 -05:00
|
|
|
"zoom": zoom.Asset,
|
2017-11-30 14:55:44 -06:00
|
|
|
}
|
|
|
|
|
|
2018-05-23 14:26:35 -04:00
|
|
|
func (a *App) notifyPluginStatusesChanged() error {
|
|
|
|
|
pluginStatuses, err := a.GetClusterPluginStatuses()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Notify any system admins.
|
|
|
|
|
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_STATUSES_CHANGED, "", "", "", nil)
|
|
|
|
|
message.Add("plugin_statuses", pluginStatuses)
|
|
|
|
|
message.Broadcast.ContainsSensitiveData = true
|
|
|
|
|
a.Publish(message)
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) setPluginStatusState(id string, state int) error {
|
|
|
|
|
if _, ok := a.pluginStatuses[id]; !ok {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a.pluginStatuses[id].State = state
|
|
|
|
|
|
|
|
|
|
return a.notifyPluginStatusesChanged()
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-06 21:53:40 -06:00
|
|
|
func (a *App) initBuiltInPlugins() {
|
2017-09-11 10:02:02 -05:00
|
|
|
plugins := map[string]builtinplugin.Plugin{
|
2017-09-01 14:28:15 -04:00
|
|
|
"ldapextras": &ldapextras.Plugin{},
|
2017-08-02 01:36:54 -07:00
|
|
|
}
|
|
|
|
|
for id, p := range plugins {
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
mlog.Debug("Initializing built-in plugin", mlog.String("plugin_id", id))
|
2017-09-11 10:02:02 -05:00
|
|
|
api := &BuiltInPluginAPI{
|
2017-08-02 01:36:54 -07:00
|
|
|
id: id,
|
2017-09-06 17:12:54 -05:00
|
|
|
router: a.Srv.Router.PathPrefix("/plugins/" + id).Subrouter(),
|
2017-09-11 10:02:02 -05:00
|
|
|
app: a,
|
2017-08-02 01:36:54 -07:00
|
|
|
}
|
|
|
|
|
p.Initialize(api)
|
|
|
|
|
}
|
2018-01-12 08:02:11 -06:00
|
|
|
a.AddConfigListener(func(before, after *model.Config) {
|
2017-08-02 01:36:54 -07:00
|
|
|
for _, p := range plugins {
|
|
|
|
|
p.OnConfigurationChange()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
for _, p := range plugins {
|
|
|
|
|
p.OnConfigurationChange()
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-01 09:00:27 -04:00
|
|
|
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
func (a *App) setPluginsActive(activate bool) {
|
2017-09-11 10:02:02 -05:00
|
|
|
if a.PluginEnv == nil {
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
mlog.Error(fmt.Sprintf("Cannot setPluginsActive(%t): plugin env not initialized", activate))
|
2017-09-01 09:00:27 -04:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-11 10:02:02 -05:00
|
|
|
plugins, err := a.PluginEnv.Plugins()
|
2017-09-01 09:00:27 -04:00
|
|
|
if err != nil {
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
mlog.Error(fmt.Sprintf("Cannot setPluginsActive(%t)", activate), mlog.Err(err))
|
2017-09-01 09:00:27 -04:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, plugin := range plugins {
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
if plugin.Manifest == nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-23 14:26:35 -04:00
|
|
|
enabled := false
|
|
|
|
|
if state, ok := a.Config().PluginSettings.PluginStates[plugin.Manifest.Id]; ok {
|
|
|
|
|
enabled = state.Enable
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a.pluginStatuses[plugin.Manifest.Id] = &model.PluginStatus{
|
|
|
|
|
ClusterId: a.GetClusterId(),
|
|
|
|
|
PluginId: plugin.Manifest.Id,
|
|
|
|
|
PluginPath: filepath.Dir(plugin.ManifestPath),
|
|
|
|
|
IsSandboxed: a.IsPluginSandboxSupported,
|
|
|
|
|
Name: plugin.Manifest.Name,
|
|
|
|
|
Description: plugin.Manifest.Description,
|
|
|
|
|
Version: plugin.Manifest.Version,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if activate && enabled {
|
|
|
|
|
a.setPluginActive(plugin, activate)
|
|
|
|
|
} else if !activate {
|
|
|
|
|
a.setPluginActive(plugin, activate)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := a.notifyPluginStatusesChanged(); err != nil {
|
|
|
|
|
mlog.Error("failed to notify plugin status changed", mlog.Err(err))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) setPluginActiveById(id string, activate bool) {
|
|
|
|
|
plugins, err := a.PluginEnv.Plugins()
|
|
|
|
|
if err != nil {
|
|
|
|
|
mlog.Error(fmt.Sprintf("Cannot setPluginActiveById(%t)", activate), mlog.String("plugin_id", id), mlog.Err(err))
|
|
|
|
|
return
|
|
|
|
|
}
|
2017-10-25 08:17:17 -04:00
|
|
|
|
2018-05-23 14:26:35 -04:00
|
|
|
for _, plugin := range plugins {
|
|
|
|
|
if plugin.Manifest != nil && plugin.Manifest.Id == id {
|
|
|
|
|
a.setPluginActive(plugin, activate)
|
2017-10-25 08:17:17 -04:00
|
|
|
}
|
2018-05-23 14:26:35 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) setPluginActive(plugin *model.BundleInfo, activate bool) {
|
|
|
|
|
if plugin.Manifest == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
2017-10-25 08:17:17 -04:00
|
|
|
|
2018-05-23 14:26:35 -04:00
|
|
|
id := plugin.Manifest.Id
|
2017-10-25 08:17:17 -04:00
|
|
|
|
2018-05-23 14:26:35 -04:00
|
|
|
active := a.PluginEnv.IsPluginActive(id)
|
|
|
|
|
|
|
|
|
|
if activate {
|
|
|
|
|
if !active {
|
2018-03-13 10:32:24 -04:00
|
|
|
if err := a.activatePlugin(plugin.Manifest); err != nil {
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
mlog.Error("Plugin failed to activate", mlog.String("plugin_id", plugin.Manifest.Id), mlog.String("err", err.DetailedError))
|
2017-10-25 08:17:17 -04:00
|
|
|
}
|
2018-05-23 14:26:35 -04:00
|
|
|
}
|
2017-10-25 08:17:17 -04:00
|
|
|
|
2018-05-23 14:26:35 -04:00
|
|
|
} else if !activate {
|
|
|
|
|
if active {
|
2017-12-08 13:55:41 -06:00
|
|
|
if err := a.deactivatePlugin(plugin.Manifest); err != nil {
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
mlog.Error("Plugin failed to deactivate", mlog.String("plugin_id", plugin.Manifest.Id), mlog.String("err", err.DetailedError))
|
2017-10-25 08:17:17 -04:00
|
|
|
}
|
2018-05-23 14:26:35 -04:00
|
|
|
} else {
|
|
|
|
|
if err := a.setPluginStatusState(plugin.Manifest.Id, model.PluginStateNotRunning); err != nil {
|
|
|
|
|
mlog.Error("Plugin status state failed to update", mlog.String("plugin_id", plugin.Manifest.Id), mlog.String("err", err.Error()))
|
|
|
|
|
}
|
2017-12-08 13:55:41 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-10-25 08:17:17 -04:00
|
|
|
|
2018-03-13 10:32:24 -04:00
|
|
|
func (a *App) activatePlugin(manifest *model.Manifest) *model.AppError {
|
2018-05-23 14:26:35 -04:00
|
|
|
mlog.Debug("Activating plugin", mlog.String("plugin_id", manifest.Id))
|
|
|
|
|
|
|
|
|
|
if err := a.setPluginStatusState(manifest.Id, model.PluginStateStarting); err != nil {
|
|
|
|
|
return model.NewAppError("activatePlugin", "app.plugin.set_plugin_status_state.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onError := func(err error) {
|
|
|
|
|
mlog.Debug("Plugin failed to stay running", mlog.String("plugin_id", manifest.Id), mlog.Err(err))
|
|
|
|
|
|
|
|
|
|
if err := a.setPluginStatusState(manifest.Id, model.PluginStateFailedToStayRunning); err != nil {
|
|
|
|
|
mlog.Error("Failed to record plugin status", mlog.String("plugin_id", manifest.Id), mlog.Err(err))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := a.PluginEnv.ActivatePlugin(manifest.Id, onError); err != nil {
|
|
|
|
|
if err := a.setPluginStatusState(manifest.Id, model.PluginStateFailedToStart); err != nil {
|
|
|
|
|
return model.NewAppError("activatePlugin", "app.plugin.activate.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return model.NewAppError("activatePlugin", "app.plugin.activate.app_error", nil, err.Error(), http.StatusBadRequest)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := a.setPluginStatusState(manifest.Id, model.PluginStateRunning); err != nil {
|
2018-03-13 10:32:24 -04:00
|
|
|
return model.NewAppError("activatePlugin", "app.plugin.activate.app_error", nil, err.Error(), http.StatusBadRequest)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if manifest.HasClient() {
|
|
|
|
|
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_ACTIVATED, "", "", "", nil)
|
|
|
|
|
message.Add("manifest", manifest.ClientManifest())
|
|
|
|
|
a.Publish(message)
|
|
|
|
|
}
|
|
|
|
|
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
mlog.Info("Activated plugin", mlog.String("plugin_id", manifest.Id))
|
2018-03-13 10:32:24 -04:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-08 13:55:41 -06:00
|
|
|
func (a *App) deactivatePlugin(manifest *model.Manifest) *model.AppError {
|
2018-05-23 14:26:35 -04:00
|
|
|
mlog.Debug("Deactivating plugin", mlog.String("plugin_id", manifest.Id))
|
|
|
|
|
|
|
|
|
|
if err := a.setPluginStatusState(manifest.Id, model.PluginStateStopping); err != nil {
|
|
|
|
|
return model.NewAppError("EnablePlugin", "app.plugin.deactivate.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-08 13:55:41 -06:00
|
|
|
if err := a.PluginEnv.DeactivatePlugin(manifest.Id); err != nil {
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
return model.NewAppError("deactivatePlugin", "app.plugin.deactivate.app_error", nil, err.Error(), http.StatusBadRequest)
|
2017-12-08 13:55:41 -06:00
|
|
|
}
|
2017-10-25 08:17:17 -04:00
|
|
|
|
2017-12-08 13:55:41 -06:00
|
|
|
a.UnregisterPluginCommands(manifest.Id)
|
|
|
|
|
|
|
|
|
|
if manifest.HasClient() {
|
|
|
|
|
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_DEACTIVATED, "", "", "", nil)
|
|
|
|
|
message.Add("manifest", manifest.ClientManifest())
|
|
|
|
|
a.Publish(message)
|
2017-09-01 09:00:27 -04:00
|
|
|
}
|
2017-12-08 13:55:41 -06:00
|
|
|
|
2018-05-23 14:26:35 -04:00
|
|
|
if err := a.setPluginStatusState(manifest.Id, model.PluginStateNotRunning); err != nil {
|
|
|
|
|
return model.NewAppError("deactivatePlugin", "app.plugin.deactivate.app_error", nil, err.Error(), http.StatusBadRequest)
|
|
|
|
|
}
|
|
|
|
|
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
mlog.Info("Deactivated plugin", mlog.String("plugin_id", manifest.Id))
|
2017-12-08 13:55:41 -06:00
|
|
|
return nil
|
2017-09-01 09:00:27 -04:00
|
|
|
}
|
|
|
|
|
|
2017-10-25 08:17:17 -04:00
|
|
|
// InstallPlugin unpacks and installs a plugin but does not activate it.
|
|
|
|
|
func (a *App) InstallPlugin(pluginFile io.Reader) (*model.Manifest, *model.AppError) {
|
2017-11-30 14:55:44 -06:00
|
|
|
return a.installPlugin(pluginFile, false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) installPlugin(pluginFile io.Reader, allowPrepackaged bool) (*model.Manifest, *model.AppError) {
|
2017-10-18 15:36:43 -07:00
|
|
|
if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable {
|
2017-11-30 14:55:44 -06:00
|
|
|
return nil, model.NewAppError("installPlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
|
2017-09-01 09:00:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tmpDir, err := ioutil.TempDir("", "plugintmp")
|
|
|
|
|
if err != nil {
|
2017-11-30 14:55:44 -06:00
|
|
|
return nil, model.NewAppError("installPlugin", "app.plugin.filesystem.app_error", nil, err.Error(), http.StatusInternalServerError)
|
2017-09-01 09:00:27 -04:00
|
|
|
}
|
2017-09-11 10:02:02 -05:00
|
|
|
defer os.RemoveAll(tmpDir)
|
2017-09-01 09:00:27 -04:00
|
|
|
|
2017-09-11 10:02:02 -05:00
|
|
|
if err := utils.ExtractTarGz(pluginFile, tmpDir); err != nil {
|
2017-11-30 14:55:44 -06:00
|
|
|
return nil, model.NewAppError("installPlugin", "app.plugin.extract.app_error", nil, err.Error(), http.StatusBadRequest)
|
2017-09-01 09:00:27 -04:00
|
|
|
}
|
|
|
|
|
|
2017-09-11 10:02:02 -05:00
|
|
|
tmpPluginDir := tmpDir
|
|
|
|
|
dir, err := ioutil.ReadDir(tmpDir)
|
|
|
|
|
if err != nil {
|
2017-11-30 14:55:44 -06:00
|
|
|
return nil, model.NewAppError("installPlugin", "app.plugin.filesystem.app_error", nil, err.Error(), http.StatusInternalServerError)
|
2017-09-01 09:00:27 -04:00
|
|
|
}
|
|
|
|
|
|
2017-09-11 10:02:02 -05:00
|
|
|
if len(dir) == 1 && dir[0].IsDir() {
|
|
|
|
|
tmpPluginDir = filepath.Join(tmpPluginDir, dir[0].Name())
|
2017-09-01 09:00:27 -04:00
|
|
|
}
|
|
|
|
|
|
2017-09-11 10:02:02 -05:00
|
|
|
manifest, _, err := model.FindManifest(tmpPluginDir)
|
2017-09-01 09:00:27 -04:00
|
|
|
if err != nil {
|
2017-11-30 14:55:44 -06:00
|
|
|
return nil, model.NewAppError("installPlugin", "app.plugin.manifest.app_error", nil, err.Error(), http.StatusBadRequest)
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-23 14:26:35 -04:00
|
|
|
_, isPrepackaged := prepackagedPlugins[manifest.Id]
|
|
|
|
|
if isPrepackaged && !allowPrepackaged {
|
2017-11-30 14:55:44 -06:00
|
|
|
return nil, model.NewAppError("installPlugin", "app.plugin.prepackaged.app_error", nil, "", http.StatusBadRequest)
|
2017-11-21 14:15:10 -05:00
|
|
|
}
|
|
|
|
|
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
if !plugin.IsValidId(manifest.Id) {
|
|
|
|
|
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)
|
2017-12-05 18:19:33 -05:00
|
|
|
}
|
|
|
|
|
|
2017-11-21 14:15:10 -05:00
|
|
|
bundles, err := a.PluginEnv.Plugins()
|
|
|
|
|
if err != nil {
|
2017-11-30 14:55:44 -06:00
|
|
|
return nil, model.NewAppError("installPlugin", "app.plugin.install.app_error", nil, err.Error(), http.StatusInternalServerError)
|
2017-11-21 14:15:10 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, bundle := range bundles {
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
if bundle.Manifest != nil && bundle.Manifest.Id == manifest.Id {
|
2017-11-30 14:55:44 -06:00
|
|
|
return nil, model.NewAppError("installPlugin", "app.plugin.install_id.app_error", nil, "", http.StatusBadRequest)
|
2017-11-21 14:15:10 -05:00
|
|
|
}
|
2017-09-01 09:00:27 -04:00
|
|
|
}
|
|
|
|
|
|
2018-05-23 14:26:35 -04:00
|
|
|
pluginPath := filepath.Join(a.PluginEnv.SearchPath(), manifest.Id)
|
|
|
|
|
err = utils.CopyDir(tmpPluginDir, pluginPath)
|
2017-09-01 09:00:27 -04:00
|
|
|
if err != nil {
|
2017-11-30 14:55:44 -06:00
|
|
|
return nil, model.NewAppError("installPlugin", "app.plugin.mvdir.app_error", nil, err.Error(), http.StatusInternalServerError)
|
2017-09-01 09:00:27 -04:00
|
|
|
}
|
|
|
|
|
|
2018-05-23 14:26:35 -04:00
|
|
|
a.pluginStatuses[manifest.Id] = &model.PluginStatus{
|
|
|
|
|
ClusterId: a.GetClusterId(),
|
|
|
|
|
PluginId: manifest.Id,
|
|
|
|
|
PluginPath: pluginPath,
|
|
|
|
|
State: model.PluginStateNotRunning,
|
|
|
|
|
IsSandboxed: a.IsPluginSandboxSupported,
|
|
|
|
|
IsPrepackaged: isPrepackaged,
|
|
|
|
|
Name: manifest.Name,
|
|
|
|
|
Description: manifest.Description,
|
|
|
|
|
Version: manifest.Version,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := a.notifyPluginStatusesChanged(); err != nil {
|
|
|
|
|
mlog.Error("failed to notify plugin status changed", mlog.Err(err))
|
|
|
|
|
}
|
2017-09-01 09:00:27 -04:00
|
|
|
|
2017-10-25 08:17:17 -04:00
|
|
|
return manifest, nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-23 14:26:35 -04:00
|
|
|
// GetPlugins returned the plugins installed on this server, including the manifests needed to
|
|
|
|
|
// enable plugins with web functionality.
|
2017-11-30 14:55:44 -06:00
|
|
|
func (a *App) GetPlugins() (*model.PluginsResponse, *model.AppError) {
|
2017-10-25 08:17:17 -04:00
|
|
|
if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable {
|
2017-11-30 14:55:44 -06:00
|
|
|
return nil, model.NewAppError("GetPlugins", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
|
2017-10-25 08:17:17 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
plugins, err := a.PluginEnv.Plugins()
|
2017-09-01 09:00:27 -04:00
|
|
|
if err != nil {
|
2017-11-30 14:55:44 -06:00
|
|
|
return nil, model.NewAppError("GetPlugins", "app.plugin.get_plugins.app_error", nil, err.Error(), http.StatusInternalServerError)
|
2017-09-01 09:00:27 -04:00
|
|
|
}
|
|
|
|
|
|
2017-11-30 14:55:44 -06:00
|
|
|
resp := &model.PluginsResponse{Active: []*model.PluginInfo{}, Inactive: []*model.PluginInfo{}}
|
2017-10-25 08:17:17 -04:00
|
|
|
for _, plugin := range plugins {
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
if plugin.Manifest == nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-30 14:55:44 -06:00
|
|
|
info := &model.PluginInfo{
|
|
|
|
|
Manifest: *plugin.Manifest,
|
|
|
|
|
}
|
|
|
|
|
_, info.Prepackaged = prepackagedPlugins[plugin.Manifest.Id]
|
2017-10-25 08:17:17 -04:00
|
|
|
if a.PluginEnv.IsPluginActive(plugin.Manifest.Id) {
|
2017-11-30 14:55:44 -06:00
|
|
|
resp.Active = append(resp.Active, info)
|
2017-10-25 08:17:17 -04:00
|
|
|
} else {
|
2017-11-30 14:55:44 -06:00
|
|
|
resp.Inactive = append(resp.Inactive, info)
|
2017-10-25 08:17:17 -04:00
|
|
|
}
|
2017-09-15 08:51:46 -04:00
|
|
|
}
|
|
|
|
|
|
2017-10-25 08:17:17 -04:00
|
|
|
return resp, nil
|
2017-09-01 09:00:27 -04:00
|
|
|
}
|
|
|
|
|
|
2017-09-06 17:12:54 -05:00
|
|
|
func (a *App) GetActivePluginManifests() ([]*model.Manifest, *model.AppError) {
|
2017-10-18 15:36:43 -07:00
|
|
|
if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable {
|
2017-09-01 09:00:27 -04:00
|
|
|
return nil, model.NewAppError("GetActivePluginManifests", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-15 08:51:46 -04:00
|
|
|
plugins := a.PluginEnv.ActivePlugins()
|
2017-09-01 09:00:27 -04:00
|
|
|
|
|
|
|
|
manifests := make([]*model.Manifest, len(plugins))
|
|
|
|
|
for i, plugin := range plugins {
|
|
|
|
|
manifests[i] = plugin.Manifest
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return manifests, nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-23 14:26:35 -04:00
|
|
|
// GetPluginStatuses returns the status for plugins installed on this server.
|
|
|
|
|
func (a *App) GetPluginStatuses() (model.PluginStatuses, *model.AppError) {
|
|
|
|
|
if !*a.Config().PluginSettings.Enable {
|
|
|
|
|
return nil, model.NewAppError("GetPluginStatuses", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pluginStatuses := make([]*model.PluginStatus, 0, len(a.pluginStatuses))
|
|
|
|
|
for _, pluginStatus := range a.pluginStatuses {
|
|
|
|
|
pluginStatuses = append(pluginStatuses, pluginStatus)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pluginStatuses, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetClusterPluginStatuses returns the status for plugins installed anywhere in the cluster.
|
|
|
|
|
func (a *App) GetClusterPluginStatuses() (model.PluginStatuses, *model.AppError) {
|
|
|
|
|
pluginStatuses, err := a.GetPluginStatuses()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if a.Cluster != nil && *a.Config().ClusterSettings.Enable {
|
|
|
|
|
clusterPluginStatuses, err := a.Cluster.GetPluginStatuses()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, model.NewAppError("GetClusterPluginStatuses", "app.plugin.get_cluster_plugin_statuses.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pluginStatuses = append(pluginStatuses, clusterPluginStatuses...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pluginStatuses, nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-06 17:12:54 -05:00
|
|
|
func (a *App) RemovePlugin(id string) *model.AppError {
|
2017-11-30 14:55:44 -06:00
|
|
|
return a.removePlugin(id, false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) removePlugin(id string, allowPrepackaged bool) *model.AppError {
|
2017-10-18 15:36:43 -07:00
|
|
|
if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable {
|
2017-11-30 14:55:44 -06:00
|
|
|
return model.NewAppError("removePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if _, ok := prepackagedPlugins[id]; ok && !allowPrepackaged {
|
|
|
|
|
return model.NewAppError("removePlugin", "app.plugin.prepackaged.app_error", nil, "", http.StatusBadRequest)
|
2017-09-01 09:00:27 -04:00
|
|
|
}
|
|
|
|
|
|
2017-10-25 08:17:17 -04:00
|
|
|
plugins, err := a.PluginEnv.Plugins()
|
|
|
|
|
if err != nil {
|
2017-11-30 14:55:44 -06:00
|
|
|
return model.NewAppError("removePlugin", "app.plugin.deactivate.app_error", nil, err.Error(), http.StatusBadRequest)
|
2017-10-25 08:17:17 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var manifest *model.Manifest
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
var pluginPath string
|
2017-09-15 08:51:46 -04:00
|
|
|
for _, p := range plugins {
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
if p.Manifest != nil && p.Manifest.Id == id {
|
2017-09-15 08:51:46 -04:00
|
|
|
manifest = p.Manifest
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
pluginPath = filepath.Dir(p.ManifestPath)
|
2017-09-15 08:51:46 -04:00
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-25 08:17:17 -04:00
|
|
|
if manifest == nil {
|
2017-11-30 14:55:44 -06:00
|
|
|
return model.NewAppError("removePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusBadRequest)
|
2017-10-25 08:17:17 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if a.PluginEnv.IsPluginActive(id) {
|
2017-12-08 13:55:41 -06:00
|
|
|
err := a.deactivatePlugin(manifest)
|
2017-10-25 08:17:17 -04:00
|
|
|
if err != nil {
|
2017-12-08 13:55:41 -06:00
|
|
|
return err
|
2017-10-25 08:17:17 -04:00
|
|
|
}
|
2017-09-01 09:00:27 -04:00
|
|
|
}
|
|
|
|
|
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
err = os.RemoveAll(pluginPath)
|
2017-09-01 09:00:27 -04:00
|
|
|
if err != nil {
|
2017-11-30 14:55:44 -06:00
|
|
|
return model.NewAppError("removePlugin", "app.plugin.remove.app_error", nil, err.Error(), http.StatusInternalServerError)
|
2017-09-01 09:00:27 -04:00
|
|
|
}
|
|
|
|
|
|
2018-05-23 14:26:35 -04:00
|
|
|
delete(a.pluginStatuses, manifest.Id)
|
|
|
|
|
if err := a.notifyPluginStatusesChanged(); err != nil {
|
|
|
|
|
mlog.Error("failed to notify plugin status changed", mlog.Err(err))
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-25 08:17:17 -04:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-23 14:26:35 -04:00
|
|
|
// EnablePlugin will set the config for an installed plugin to enabled, triggering asynchronous
|
|
|
|
|
// activation if inactive anywhere in the cluster.
|
2017-10-25 08:17:17 -04:00
|
|
|
func (a *App) EnablePlugin(id string) *model.AppError {
|
|
|
|
|
if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable {
|
2017-11-30 14:55:44 -06:00
|
|
|
return model.NewAppError("EnablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
|
2017-10-25 08:17:17 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-23 14:26:35 -04:00
|
|
|
if err := a.setPluginStatusState(manifest.Id, model.PluginStateStarting); err != nil {
|
|
|
|
|
return model.NewAppError("EnablePlugin", "app.plugin.set_plugin_status_state.app_error", nil, err.Error(), http.StatusInternalServerError)
|
2018-03-13 10:32:24 -04:00
|
|
|
}
|
|
|
|
|
|
2017-10-31 09:39:31 -05:00
|
|
|
a.UpdateConfig(func(cfg *model.Config) {
|
|
|
|
|
cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: true}
|
|
|
|
|
})
|
2017-10-25 08:17:17 -04:00
|
|
|
|
2017-10-31 09:39:31 -05:00
|
|
|
if err := a.SaveConfig(a.Config(), true); err != nil {
|
2018-03-13 11:03:12 -05:00
|
|
|
if err.Id == "ent.cluster.save_config.error" {
|
|
|
|
|
return model.NewAppError("EnablePlugin", "app.plugin.cluster.save_config.app_error", nil, "", http.StatusInternalServerError)
|
|
|
|
|
}
|
2017-10-25 08:17:17 -04:00
|
|
|
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 {
|
2017-11-30 14:55:44 -06:00
|
|
|
return model.NewAppError("DisablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
|
2017-10-25 08:17:17 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-23 14:26:35 -04:00
|
|
|
if err := a.setPluginStatusState(manifest.Id, model.PluginStateStopping); err != nil {
|
|
|
|
|
return model.NewAppError("EnablePlugin", "app.plugin.set_plugin_status_state.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-31 09:39:31 -05:00
|
|
|
a.UpdateConfig(func(cfg *model.Config) {
|
|
|
|
|
cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: false}
|
|
|
|
|
})
|
2017-10-25 08:17:17 -04:00
|
|
|
|
2017-10-31 09:39:31 -05:00
|
|
|
if err := a.SaveConfig(a.Config(), true); err != nil {
|
2017-10-25 08:17:17 -04:00
|
|
|
return model.NewAppError("DisablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError)
|
2017-09-01 09:00:27 -04:00
|
|
|
}
|
|
|
|
|
|
2017-09-15 08:51:46 -04:00
|
|
|
return nil
|
2017-09-01 09:00:27 -04:00
|
|
|
}
|
2017-09-11 10:02:02 -05:00
|
|
|
|
2017-12-08 13:55:41 -06:00
|
|
|
func (a *App) InitPlugins(pluginPath, webappPath string, supervisorOverride pluginenv.SupervisorProviderFunc) {
|
2018-05-23 14:26:35 -04:00
|
|
|
if a.PluginEnv != nil {
|
2017-09-11 10:02:02 -05:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-23 14:26:35 -04:00
|
|
|
if !*a.Config().PluginSettings.Enable {
|
2017-10-06 13:58:23 -04:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-27 12:49:45 -07:00
|
|
|
mlog.Info("Starting up plugins")
|
2017-09-11 10:02:02 -05:00
|
|
|
|
2018-05-23 14:26:35 -04:00
|
|
|
a.pluginStatuses = make(map[string]*model.PluginStatus)
|
|
|
|
|
|
2017-11-30 14:55:44 -06:00
|
|
|
if err := os.Mkdir(pluginPath, 0744); err != nil && !os.IsExist(err) {
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
mlog.Error("Failed to start up plugins", mlog.Err(err))
|
2017-09-11 10:02:02 -05:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-30 14:55:44 -06:00
|
|
|
if err := os.Mkdir(webappPath, 0744); err != nil && !os.IsExist(err) {
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
mlog.Error("Failed to start up plugins", mlog.Err(err))
|
2017-09-15 08:51:46 -04:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-08 13:55:41 -06:00
|
|
|
options := []pluginenv.Option{
|
2017-09-11 10:02:02 -05:00
|
|
|
pluginenv.SearchPath(pluginPath),
|
|
|
|
|
pluginenv.WebappPath(webappPath),
|
|
|
|
|
pluginenv.APIProvider(func(m *model.Manifest) (plugin.API, error) {
|
|
|
|
|
return &PluginAPI{
|
|
|
|
|
id: m.Id,
|
|
|
|
|
app: a,
|
2017-11-27 17:23:35 -05:00
|
|
|
keyValueStore: &PluginKeyValueStore{
|
|
|
|
|
id: m.Id,
|
|
|
|
|
app: a,
|
|
|
|
|
},
|
2017-09-11 10:02:02 -05:00
|
|
|
}, nil
|
|
|
|
|
}),
|
2017-12-08 13:55:41 -06:00
|
|
|
}
|
|
|
|
|
|
2018-06-04 12:34:47 -04:00
|
|
|
a.IsPluginSandboxSupported = sandbox.CheckSupport() == nil
|
2018-05-23 14:26:35 -04:00
|
|
|
|
|
|
|
|
if supervisorOverride != nil {
|
|
|
|
|
options = append(options, pluginenv.SupervisorProvider(supervisorOverride))
|
|
|
|
|
} else if a.IsPluginSandboxSupported {
|
2018-01-15 11:21:06 -06:00
|
|
|
options = append(options, pluginenv.SupervisorProvider(sandbox.SupervisorProvider))
|
2018-05-23 14:26:35 -04:00
|
|
|
} else {
|
|
|
|
|
options = append(options, pluginenv.SupervisorProvider(rpcplugin.SupervisorProvider))
|
2017-12-08 13:55:41 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if env, err := pluginenv.New(options...); err != nil {
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
mlog.Error("Failed to start up plugins", mlog.Err(err))
|
2017-09-11 10:02:02 -05:00
|
|
|
return
|
2017-11-30 14:55:44 -06:00
|
|
|
} else {
|
|
|
|
|
a.PluginEnv = env
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for id, asset := range prepackagedPlugins {
|
|
|
|
|
if tarball, err := asset("plugin.tar.gz"); err != nil {
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
mlog.Error("Failed to install prepackaged plugin", mlog.Err(err))
|
2017-11-30 14:55:44 -06:00
|
|
|
} else if tarball != nil {
|
|
|
|
|
a.removePlugin(id, true)
|
|
|
|
|
if _, err := a.installPlugin(bytes.NewReader(tarball), true); err != nil {
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
mlog.Error("Failed to install prepackaged plugin", mlog.Err(err))
|
2017-11-30 14:55:44 -06:00
|
|
|
}
|
2017-12-05 16:35:46 -05:00
|
|
|
if _, ok := a.Config().PluginSettings.PluginStates[id]; !ok && id != "zoom" {
|
2017-11-30 14:55:44 -06:00
|
|
|
if err := a.EnablePlugin(id); err != nil {
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
mlog.Error("Failed to enable prepackaged plugin", mlog.Err(err))
|
2017-11-30 14:55:44 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-11 10:02:02 -05:00
|
|
|
}
|
|
|
|
|
|
2018-01-12 08:02:11 -06:00
|
|
|
a.RemoveConfigListener(a.PluginConfigListenerId)
|
2018-05-23 14:26:35 -04:00
|
|
|
a.PluginConfigListenerId = a.AddConfigListener(func(oldCfg *model.Config, cfg *model.Config) {
|
2017-10-25 08:17:17 -04:00
|
|
|
if a.PluginEnv == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-23 14:26:35 -04:00
|
|
|
if *oldCfg.PluginSettings.Enable != *cfg.PluginSettings.Enable {
|
|
|
|
|
a.setPluginsActive(*cfg.PluginSettings.Enable)
|
|
|
|
|
} else {
|
|
|
|
|
plugins := map[string]bool{}
|
|
|
|
|
for id := range oldCfg.PluginSettings.PluginStates {
|
|
|
|
|
plugins[id] = true
|
|
|
|
|
}
|
|
|
|
|
for id := range cfg.PluginSettings.PluginStates {
|
|
|
|
|
plugins[id] = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for id := range plugins {
|
|
|
|
|
oldPluginState := oldCfg.PluginSettings.PluginStates[id]
|
|
|
|
|
pluginState := cfg.PluginSettings.PluginStates[id]
|
|
|
|
|
|
|
|
|
|
wasEnabled := oldPluginState != nil && oldPluginState.Enable
|
|
|
|
|
isEnabled := pluginState != nil && pluginState.Enable
|
|
|
|
|
|
|
|
|
|
if wasEnabled != isEnabled {
|
|
|
|
|
a.setPluginActiveById(id, isEnabled)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-11-03 17:47:52 -04:00
|
|
|
|
2017-09-11 10:02:02 -05:00
|
|
|
for _, err := range a.PluginEnv.Hooks().OnConfigurationChange() {
|
2018-04-27 12:49:45 -07:00
|
|
|
mlog.Error(err.Error())
|
2017-09-11 10:02:02 -05:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
MM-8622: improved plugin error handling (#8692)
* don't report an error on plugin activation if already active
* improved plugin logging events
Log an error when a plugin's ServeHTTP fails, or when it unexpectedly
terminates.
Restart a plugin at most three times, allowing its failure to later
bubble up under the "failed to stay running" status.
* clarified plugin activation/deactivation
Avoid repeatedly activating when any configuration bit changes. Improved
logging.
* constrain plugin ids to ^[a-zA-Z0-9-_\.]+$ and enforce minimum length
Previously, the plugin id was used unsanitized to relocate the plugin
bundle, which allowed writing outside the `plugins/` directory by using
an `id` containing `../`.
Similarly, an empty string was accepted as an id and led to unexpected
error messages.
* remove plugins by manifest path, not id
If the id within the manifest ever diverges from the actual plugin
location, it becomes impossible to remove via the API. Instead, if the
plugin is found by id, remove the path containing the manifest.
* ignore plugins with nil manifests
If a plugin was detected, but had a manifest that couldn't be parsed, it
will be left nil but still be listed among the packages. Skip over these
in most cases to avoid segfaults.
* leverage mlog more effectively for plugins
* build issues
2018-05-01 10:34:12 -04:00
|
|
|
a.setPluginsActive(true)
|
2017-09-11 10:02:02 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) ServePluginRequest(w http.ResponseWriter, r *http.Request) {
|
2017-11-03 17:47:52 -04:00
|
|
|
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)
|
2018-04-27 12:49:45 -07:00
|
|
|
mlog.Error(err.Error())
|
2017-11-03 17:47:52 -04:00
|
|
|
w.WriteHeader(err.StatusCode)
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
w.Write([]byte(err.ToJson()))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-01 09:07:32 -06:00
|
|
|
a.servePluginRequest(w, r, a.PluginEnv.Hooks().ServeHTTP)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) servePluginRequest(w http.ResponseWriter, r *http.Request, handler http.HandlerFunc) {
|
2017-09-11 10:02:02 -05:00
|
|
|
token := ""
|
|
|
|
|
|
|
|
|
|
authHeader := r.Header.Get(model.HEADER_AUTH)
|
2017-12-01 09:07:32 -06:00
|
|
|
if strings.HasPrefix(strings.ToUpper(authHeader), model.HEADER_BEARER+" ") {
|
2017-09-11 10:02:02 -05:00
|
|
|
token = authHeader[len(model.HEADER_BEARER)+1:]
|
2017-12-01 09:07:32 -06:00
|
|
|
} else if strings.HasPrefix(strings.ToLower(authHeader), model.HEADER_TOKEN+" ") {
|
2017-09-11 10:02:02 -05:00
|
|
|
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 != "" {
|
2017-12-01 09:07:32 -06:00
|
|
|
if session, err := a.GetSession(token); session != nil && err == nil {
|
2017-09-11 10:02:02 -05:00
|
|
|
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")
|
|
|
|
|
|
2017-12-01 09:07:32 -06:00
|
|
|
params := mux.Vars(r)
|
|
|
|
|
|
2017-09-11 10:02:02 -05:00
|
|
|
newQuery := r.URL.Query()
|
|
|
|
|
newQuery.Del("access_token")
|
|
|
|
|
r.URL.RawQuery = newQuery.Encode()
|
2017-12-01 09:07:32 -06:00
|
|
|
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/plugins/"+params["plugin_id"])
|
2017-09-11 10:02:02 -05:00
|
|
|
|
2017-12-01 09:07:32 -06:00
|
|
|
handler(w, r.WithContext(context.WithValue(r.Context(), "plugin_id", params["plugin_id"])))
|
2017-09-11 10:02:02 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) ShutDownPlugins() {
|
|
|
|
|
if a.PluginEnv == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
2017-10-06 13:58:23 -04:00
|
|
|
|
2018-04-27 12:49:45 -07:00
|
|
|
mlog.Info("Shutting down plugins")
|
2017-10-06 13:58:23 -04:00
|
|
|
|
2017-09-11 10:02:02 -05:00
|
|
|
for _, err := range a.PluginEnv.Shutdown() {
|
2018-04-27 12:49:45 -07:00
|
|
|
mlog.Error(err.Error())
|
2017-09-11 10:02:02 -05:00
|
|
|
}
|
2018-01-12 08:02:11 -06:00
|
|
|
a.RemoveConfigListener(a.PluginConfigListenerId)
|
2017-09-12 14:12:29 -05:00
|
|
|
a.PluginConfigListenerId = ""
|
2017-10-06 13:58:23 -04:00
|
|
|
a.PluginEnv = nil
|
2017-09-11 10:02:02 -05:00
|
|
|
}
|
2017-11-27 17:23:35 -05:00
|
|
|
|
2017-12-05 18:19:33 -05:00
|
|
|
func getKeyHash(key string) string {
|
|
|
|
|
hash := sha256.New()
|
|
|
|
|
hash.Write([]byte(key))
|
|
|
|
|
return base64.StdEncoding.EncodeToString(hash.Sum(nil))
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-27 17:23:35 -05:00
|
|
|
func (a *App) SetPluginKey(pluginId string, key string, value []byte) *model.AppError {
|
|
|
|
|
kv := &model.PluginKeyValue{
|
|
|
|
|
PluginId: pluginId,
|
2017-12-05 18:19:33 -05:00
|
|
|
Key: getKeyHash(key),
|
2017-11-27 17:23:35 -05:00
|
|
|
Value: value,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result := <-a.Srv.Store.Plugin().SaveOrUpdate(kv)
|
|
|
|
|
|
|
|
|
|
if result.Err != nil {
|
2018-04-27 12:49:45 -07:00
|
|
|
mlog.Error(result.Err.Error())
|
2017-11-27 17:23:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result.Err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) GetPluginKey(pluginId string, key string) ([]byte, *model.AppError) {
|
2017-12-05 18:19:33 -05:00
|
|
|
result := <-a.Srv.Store.Plugin().Get(pluginId, getKeyHash(key))
|
2017-11-27 17:23:35 -05:00
|
|
|
|
|
|
|
|
if result.Err != nil {
|
|
|
|
|
if result.Err.StatusCode == http.StatusNotFound {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
2018-04-27 12:49:45 -07:00
|
|
|
mlog.Error(result.Err.Error())
|
2017-11-27 17:23:35 -05:00
|
|
|
return nil, result.Err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kv := result.Data.(*model.PluginKeyValue)
|
|
|
|
|
|
|
|
|
|
return kv.Value, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) DeletePluginKey(pluginId string, key string) *model.AppError {
|
2017-12-05 18:19:33 -05:00
|
|
|
result := <-a.Srv.Store.Plugin().Delete(pluginId, getKeyHash(key))
|
2017-11-27 17:23:35 -05:00
|
|
|
|
|
|
|
|
if result.Err != nil {
|
2018-04-27 12:49:45 -07:00
|
|
|
mlog.Error(result.Err.Error())
|
2017-11-27 17:23:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result.Err
|
|
|
|
|
}
|
2017-12-08 13:55:41 -06:00
|
|
|
|
|
|
|
|
type PluginCommand struct {
|
|
|
|
|
Command *model.Command
|
|
|
|
|
PluginId string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) RegisterPluginCommand(pluginId string, command *model.Command) error {
|
|
|
|
|
if command.Trigger == "" {
|
|
|
|
|
return fmt.Errorf("invalid command")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
command = &model.Command{
|
|
|
|
|
Trigger: strings.ToLower(command.Trigger),
|
|
|
|
|
TeamId: command.TeamId,
|
|
|
|
|
AutoComplete: command.AutoComplete,
|
|
|
|
|
AutoCompleteDesc: command.AutoCompleteDesc,
|
2018-02-07 07:21:40 -06:00
|
|
|
AutoCompleteHint: command.AutoCompleteHint,
|
2017-12-08 13:55:41 -06:00
|
|
|
DisplayName: command.DisplayName,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a.pluginCommandsLock.Lock()
|
|
|
|
|
defer a.pluginCommandsLock.Unlock()
|
|
|
|
|
|
|
|
|
|
for _, pc := range a.pluginCommands {
|
|
|
|
|
if pc.Command.Trigger == command.Trigger && pc.Command.TeamId == command.TeamId {
|
|
|
|
|
if pc.PluginId == pluginId {
|
|
|
|
|
pc.Command = command
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a.pluginCommands = append(a.pluginCommands, &PluginCommand{
|
|
|
|
|
Command: command,
|
|
|
|
|
PluginId: pluginId,
|
|
|
|
|
})
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) UnregisterPluginCommand(pluginId, teamId, trigger string) {
|
|
|
|
|
trigger = strings.ToLower(trigger)
|
|
|
|
|
|
|
|
|
|
a.pluginCommandsLock.Lock()
|
|
|
|
|
defer a.pluginCommandsLock.Unlock()
|
|
|
|
|
|
|
|
|
|
var remaining []*PluginCommand
|
|
|
|
|
for _, pc := range a.pluginCommands {
|
|
|
|
|
if pc.Command.TeamId != teamId || pc.Command.Trigger != trigger {
|
|
|
|
|
remaining = append(remaining, pc)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
a.pluginCommands = remaining
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) UnregisterPluginCommands(pluginId string) {
|
|
|
|
|
a.pluginCommandsLock.Lock()
|
|
|
|
|
defer a.pluginCommandsLock.Unlock()
|
|
|
|
|
|
|
|
|
|
var remaining []*PluginCommand
|
|
|
|
|
for _, pc := range a.pluginCommands {
|
|
|
|
|
if pc.PluginId != pluginId {
|
|
|
|
|
remaining = append(remaining, pc)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
a.pluginCommands = remaining
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) PluginCommandsForTeam(teamId string) []*model.Command {
|
|
|
|
|
a.pluginCommandsLock.RLock()
|
|
|
|
|
defer a.pluginCommandsLock.RUnlock()
|
|
|
|
|
|
|
|
|
|
var commands []*model.Command
|
|
|
|
|
for _, pc := range a.pluginCommands {
|
|
|
|
|
if pc.Command.TeamId == "" || pc.Command.TeamId == teamId {
|
|
|
|
|
commands = append(commands, pc.Command)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return commands
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) ExecutePluginCommand(args *model.CommandArgs) (*model.Command, *model.CommandResponse, *model.AppError) {
|
|
|
|
|
parts := strings.Split(args.Command, " ")
|
|
|
|
|
trigger := parts[0][1:]
|
|
|
|
|
trigger = strings.ToLower(trigger)
|
|
|
|
|
|
|
|
|
|
a.pluginCommandsLock.RLock()
|
|
|
|
|
defer a.pluginCommandsLock.RUnlock()
|
|
|
|
|
|
|
|
|
|
for _, pc := range a.pluginCommands {
|
|
|
|
|
if (pc.Command.TeamId == "" || pc.Command.TeamId == args.TeamId) && pc.Command.Trigger == trigger {
|
|
|
|
|
response, appErr, err := a.PluginEnv.HooksForPlugin(pc.PluginId).ExecuteCommand(args)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return pc.Command, nil, model.NewAppError("ExecutePluginCommand", "model.plugin_command.error.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
return pc.Command, response, appErr
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil, nil, nil
|
|
|
|
|
}
|
2018-05-15 13:33:47 -07:00
|
|
|
|
|
|
|
|
func (a *App) PluginsReady() bool {
|
|
|
|
|
return a.PluginEnv != nil && *a.Config().PluginSettings.Enable
|
|
|
|
|
}
|