summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoram Wilander <jwawilander@gmail.com>2017-09-15 08:51:46 -0400
committerGitHub <noreply@github.com>2017-09-15 08:51:46 -0400
commit2628022275ef64fde95545abe4634b4bd7177844 (patch)
tree25d451b81d720f44aa09b20389be7fbb75b7864e
parent2a6cd44f23e1b3207debaa73801f0c63a2c81126 (diff)
downloadchat-2628022275ef64fde95545abe4634b4bd7177844.tar.gz
chat-2628022275ef64fde95545abe4634b4bd7177844.tar.bz2
chat-2628022275ef64fde95545abe4634b4bd7177844.zip
PLT-7622 Improvements to server handling of webapp plugins (#7445)
* Improvements to server handling of webapp plugins * Fix newline * Update manifest function names
-rw-r--r--api4/plugin.go24
-rw-r--r--api4/plugin_test.go26
-rw-r--r--api4/system.go1
-rw-r--r--app/plugins.go62
-rw-r--r--cmd/platform/server.go17
-rw-r--r--model/client4.go11
-rw-r--r--model/manifest.go18
-rw-r--r--model/manifest_test.go48
-rw-r--r--model/websocket_message.go2
-rw-r--r--plugin/pluginenv/environment.go4
-rw-r--r--plugin/pluginenv/environment_test.go6
-rw-r--r--web/web.go26
12 files changed, 183 insertions, 62 deletions
diff --git a/api4/plugin.go b/api4/plugin.go
index 5b030e71d..ac1620335 100644
--- a/api4/plugin.go
+++ b/api4/plugin.go
@@ -25,6 +25,8 @@ func InitPlugin() {
BaseRoutes.Plugins.Handle("", ApiSessionRequired(getPlugins)).Methods("GET")
BaseRoutes.Plugin.Handle("", ApiSessionRequired(removePlugin)).Methods("DELETE")
+ BaseRoutes.Plugins.Handle("/webapp", ApiHandler(getWebappPlugins)).Methods("GET")
+
}
func uploadPlugin(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -118,3 +120,25 @@ func removePlugin(c *Context, w http.ResponseWriter, r *http.Request) {
ReturnStatusOK(w)
}
+
+func getWebappPlugins(c *Context, w http.ResponseWriter, r *http.Request) {
+ if !*utils.Cfg.PluginSettings.Enable {
+ c.Err = model.NewAppError("getWebappPlugins", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
+ return
+ }
+
+ manifests, err := c.App.GetActivePluginManifests()
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ clientManifests := []*model.Manifest{}
+ for _, m := range manifests {
+ if m.HasClient() {
+ clientManifests = append(clientManifests, m.ClientManifest())
+ }
+ }
+
+ w.Write([]byte(model.ManifestListToJson(clientManifests)))
+}
diff --git a/api4/plugin_test.go b/api4/plugin_test.go
index f92e58ea0..9cedccfe7 100644
--- a/api4/plugin_test.go
+++ b/api4/plugin_test.go
@@ -17,14 +17,11 @@ import (
func TestPlugin(t *testing.T) {
pluginDir, err := ioutil.TempDir("", "mm-plugin-test")
require.NoError(t, err)
- defer func() {
- os.RemoveAll(pluginDir)
- }()
+ defer os.RemoveAll(pluginDir)
+
webappDir, err := ioutil.TempDir("", "mm-webapp-test")
require.NoError(t, err)
- defer func() {
- os.RemoveAll(webappDir)
- }()
+ defer os.RemoveAll(webappDir)
th := SetupEnterprise().InitBasic().InitSystemAdmin()
defer TearDown()
@@ -50,9 +47,7 @@ func TestPlugin(t *testing.T) {
// Successful upload
manifest, resp := th.SystemAdminClient.UploadPlugin(file)
- defer func() {
- os.RemoveAll("plugins/testplugin")
- }()
+ defer os.RemoveAll("plugins/testplugin")
CheckNoError(t, resp)
assert.Equal(t, "testplugin", manifest.Id)
@@ -91,6 +86,19 @@ func TestPlugin(t *testing.T) {
_, resp = th.Client.GetPlugins()
CheckForbiddenStatus(t, resp)
+ // Successful webapp get
+ manifests, resp = th.Client.GetWebappPlugins()
+ CheckNoError(t, resp)
+
+ found = false
+ for _, m := range manifests {
+ if m.Id == manifest.Id {
+ found = true
+ }
+ }
+
+ assert.True(t, found)
+
// Successful remove
ok, resp := th.SystemAdminClient.RemovePlugin(manifest.Id)
CheckNoError(t, resp)
diff --git a/api4/system.go b/api4/system.go
index fbfbd0ef2..b50dc97a5 100644
--- a/api4/system.go
+++ b/api4/system.go
@@ -244,7 +244,6 @@ func getClientConfig(c *Context, w http.ResponseWriter, r *http.Request) {
}
respCfg["NoAccounts"] = strconv.FormatBool(c.App.IsFirstUserAccount())
- respCfg["Plugins"] = c.App.GetPluginsForClientConfig()
w.Write([]byte(model.MapToJson(respCfg)))
}
diff --git a/app/plugins.go b/app/plugins.go
index f00308a86..fb8182823 100644
--- a/app/plugins.go
+++ b/app/plugins.go
@@ -252,6 +252,12 @@ func (a *App) UnpackAndActivatePlugin(pluginFile io.Reader) (*model.Manifest, *m
return nil, model.NewAppError("UnpackAndActivatePlugin", "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())
+ Publish(message)
+ }
+
return manifest, nil
}
@@ -260,10 +266,7 @@ func (a *App) GetActivePluginManifests() ([]*model.Manifest, *model.AppError) {
return nil, model.NewAppError("GetActivePluginManifests", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
}
- plugins, err := a.PluginEnv.ActivePlugins()
- if err != nil {
- return nil, model.NewAppError("GetActivePluginManifests", "app.plugin.get_plugins.app_error", nil, err.Error(), http.StatusInternalServerError)
- }
+ plugins := a.PluginEnv.ActivePlugins()
manifests := make([]*model.Manifest, len(plugins))
for i, plugin := range plugins {
@@ -278,6 +281,15 @@ func (a *App) RemovePlugin(id string) *model.AppError {
return model.NewAppError("RemovePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
}
+ plugins := a.PluginEnv.ActivePlugins()
+ manifest := &model.Manifest{}
+ for _, p := range plugins {
+ if p.Manifest.Id == id {
+ manifest = p.Manifest
+ break
+ }
+ }
+
err := a.PluginEnv.DeactivatePlugin(id)
if err != nil {
return model.NewAppError("RemovePlugin", "app.plugin.deactivate.app_error", nil, err.Error(), http.StatusBadRequest)
@@ -288,39 +300,13 @@ func (a *App) RemovePlugin(id string) *model.AppError {
return model.NewAppError("RemovePlugin", "app.plugin.remove.app_error", nil, err.Error(), http.StatusInternalServerError)
}
- return nil
-}
-
-// Temporary WIP function/type for experimental webapp plugins
-type ClientConfigPlugin struct {
- Id string `json:"id"`
- BundlePath string `json:"bundle_path"`
-}
-
-func (a *App) GetPluginsForClientConfig() string {
- if a.PluginEnv == nil || !*utils.Cfg.PluginSettings.Enable {
- return ""
- }
-
- plugins, err := a.PluginEnv.ActivePlugins()
- if err != nil {
- return ""
- }
-
- pluginsConfig := []ClientConfigPlugin{}
- for _, plugin := range plugins {
- if plugin.Manifest.Webapp == nil {
- continue
- }
- pluginsConfig = append(pluginsConfig, ClientConfigPlugin{Id: plugin.Manifest.Id, BundlePath: plugin.Manifest.Webapp.BundlePath})
+ if manifest.HasClient() {
+ message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_DEACTIVATED, "", "", "", nil)
+ message.Add("manifest", manifest.ClientManifest())
+ Publish(message)
}
- b, err := json.Marshal(pluginsConfig)
- if err != nil {
- return ""
- }
-
- return string(b)
+ return nil
}
func (a *App) InitPlugins(pluginPath, webappPath string) {
@@ -338,6 +324,12 @@ func (a *App) InitPlugins(pluginPath, webappPath string) {
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),
diff --git a/cmd/platform/server.go b/cmd/platform/server.go
index 15c80134c..a8e724f58 100644
--- a/cmd/platform/server.go
+++ b/cmd/platform/server.go
@@ -62,11 +62,6 @@ func runServer(configFileLocation string) {
l4g.Info(utils.T("mattermost.working_dir"), pwd)
l4g.Info(utils.T("mattermost.config_file"), utils.FindConfigFile(configFileLocation))
- // Enable developer settings if this is a "dev" build
- if model.BuildNumber == "dev" {
- *utils.Cfg.ServiceSettings.EnableDeveloper = true
- }
-
if err := utils.TestFileConnection(); err != nil {
l4g.Error("Problem with file storage settings: " + err.Error())
}
@@ -79,7 +74,12 @@ func runServer(configFileLocation string) {
if model.BuildEnterpriseReady == "true" {
a.LoadLicense()
}
- a.InitPlugins("plugins", "webapp/dist")
+
+ if webappDir, ok := utils.FindDir(model.CLIENT_DIR); ok {
+ a.InitPlugins("plugins", webappDir+"/plugins")
+ } else {
+ l4g.Error("Unable to find webapp directory, could not initialize plugins")
+ }
wsapi.InitRouter()
api4.InitApi(a.Srv.Router, false)
@@ -98,6 +98,11 @@ func runServer(configFileLocation string) {
app.ReloadConfig()
+ // Enable developer settings if this is a "dev" build
+ if model.BuildNumber == "dev" {
+ *utils.Cfg.ServiceSettings.EnableDeveloper = true
+ }
+
resetStatuses(a)
a.StartServer()
diff --git a/model/client4.go b/model/client4.go
index c06975697..44c4cf6c9 100644
--- a/model/client4.go
+++ b/model/client4.go
@@ -3088,3 +3088,14 @@ func (c *Client4) RemovePlugin(id string) (bool, *Response) {
return CheckStatusOK(r), BuildResponse(r)
}
}
+
+// GetWebappPlugins will return a list of plugins that the webapp should download.
+// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE.
+func (c *Client4) GetWebappPlugins() ([]*Manifest, *Response) {
+ if r, err := c.DoApiGet(c.GetPluginsRoute()+"/webapp", ""); err != nil {
+ return nil, BuildErrorResponse(r, err)
+ } else {
+ defer closeBody(r)
+ return ManifestListFromJson(r.Body), BuildResponse(r)
+ }
+}
diff --git a/model/manifest.go b/model/manifest.go
index e61ccc8ad..b466660af 100644
--- a/model/manifest.go
+++ b/model/manifest.go
@@ -12,8 +12,9 @@ import (
type Manifest struct {
Id string `json:"id" yaml:"id"`
- Name string `json:"name" yaml:"name"`
- Description string `json:"description" yaml:"description"`
+ Name string `json:"name,omitempty" yaml:"name,omitempty"`
+ Description string `json:"description,omitempty" yaml:"description,omitempty"`
+ Version string `json:"version" yaml:"version"`
Backend *ManifestBackend `json:"backend,omitempty" yaml:"backend,omitempty"`
Webapp *ManifestWebapp `json:"webapp,omitempty" yaml:"webapp,omitempty"`
}
@@ -66,6 +67,19 @@ func ManifestListFromJson(data io.Reader) []*Manifest {
}
}
+func (m *Manifest) HasClient() bool {
+ return m.Webapp != nil
+}
+
+func (m *Manifest) ClientManifest() *Manifest {
+ cm := new(Manifest)
+ *cm = *m
+ cm.Name = ""
+ cm.Description = ""
+ cm.Backend = nil
+ return cm
+}
+
// FindManifest will find and parse the manifest in a given directory.
//
// In all cases other than a does-not-exist error, path is set to the path of the manifest file that was
diff --git a/model/manifest_test.go b/model/manifest_test.go
index 46975cf47..1ec43a217 100644
--- a/model/manifest_test.go
+++ b/model/manifest_test.go
@@ -129,3 +129,51 @@ func TestManifestJson(t *testing.T) {
assert.Equal(t, newManifestList, manifestList)
assert.Equal(t, ManifestListToJson(newManifestList), json)
}
+
+func TestManifestHasClient(t *testing.T) {
+ manifest := &Manifest{
+ Id: "theid",
+ Backend: &ManifestBackend{
+ Executable: "theexecutable",
+ },
+ Webapp: &ManifestWebapp{
+ BundlePath: "thebundlepath",
+ },
+ }
+
+ assert.True(t, manifest.HasClient())
+
+ manifest.Webapp = nil
+ assert.False(t, manifest.HasClient())
+}
+
+func TestManifestClientManifest(t *testing.T) {
+ manifest := &Manifest{
+ Id: "theid",
+ Name: "thename",
+ Description: "thedescription",
+ Version: "0.0.1",
+ Backend: &ManifestBackend{
+ Executable: "theexecutable",
+ },
+ Webapp: &ManifestWebapp{
+ BundlePath: "thebundlepath",
+ },
+ }
+
+ sanitized := manifest.ClientManifest()
+
+ assert.NotEmpty(t, sanitized.Id)
+ assert.NotEmpty(t, sanitized.Version)
+ assert.NotEmpty(t, sanitized.Webapp)
+ assert.Empty(t, sanitized.Name)
+ assert.Empty(t, sanitized.Description)
+ assert.Empty(t, sanitized.Backend)
+
+ assert.NotEmpty(t, manifest.Id)
+ assert.NotEmpty(t, manifest.Version)
+ assert.NotEmpty(t, manifest.Webapp)
+ assert.NotEmpty(t, manifest.Name)
+ assert.NotEmpty(t, manifest.Description)
+ assert.NotEmpty(t, manifest.Backend)
+}
diff --git a/model/websocket_message.go b/model/websocket_message.go
index 6b8c03427..6c55da6f0 100644
--- a/model/websocket_message.go
+++ b/model/websocket_message.go
@@ -39,6 +39,8 @@ const (
WEBSOCKET_EVENT_RESPONSE = "response"
WEBSOCKET_EVENT_EMOJI_ADDED = "emoji_added"
WEBSOCKET_EVENT_CHANNEL_VIEWED = "channel_viewed"
+ WEBSOCKET_EVENT_PLUGIN_ACTIVATED = "plugin_activated" // EXPERIMENTAL - SUBJECT TO CHANGE
+ WEBSOCKET_EVENT_PLUGIN_DEACTIVATED = "plugin_deactivated" // EXPERIMENTAL - SUBJECT TO CHANGE
)
type WebSocketMessage interface {
diff --git a/plugin/pluginenv/environment.go b/plugin/pluginenv/environment.go
index e4a7f1b3b..805b33e66 100644
--- a/plugin/pluginenv/environment.go
+++ b/plugin/pluginenv/environment.go
@@ -66,7 +66,7 @@ func (env *Environment) Plugins() ([]*model.BundleInfo, error) {
}
// Returns a list of all currently active plugins within the environment.
-func (env *Environment) ActivePlugins() ([]*model.BundleInfo, error) {
+func (env *Environment) ActivePlugins() []*model.BundleInfo {
env.mutex.RLock()
defer env.mutex.RUnlock()
@@ -75,7 +75,7 @@ func (env *Environment) ActivePlugins() ([]*model.BundleInfo, error) {
activePlugins = append(activePlugins, p.BundleInfo)
}
- return activePlugins, nil
+ return activePlugins
}
// Returns the ids of the currently active plugins.
diff --git a/plugin/pluginenv/environment_test.go b/plugin/pluginenv/environment_test.go
index f24ef8d3d..c11644b35 100644
--- a/plugin/pluginenv/environment_test.go
+++ b/plugin/pluginenv/environment_test.go
@@ -127,8 +127,7 @@ func TestEnvironment(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, plugins, 3)
- activePlugins, err := env.ActivePlugins()
- assert.NoError(t, err)
+ activePlugins := env.ActivePlugins()
assert.Len(t, activePlugins, 0)
assert.Error(t, env.ActivatePlugin("x"))
@@ -150,8 +149,7 @@ func TestEnvironment(t *testing.T) {
assert.NoError(t, env.ActivatePlugin("foo"))
assert.Equal(t, env.ActivePluginIds(), []string{"foo"})
- activePlugins, err = env.ActivePlugins()
- assert.NoError(t, err)
+ activePlugins = env.ActivePlugins()
assert.Len(t, activePlugins, 1)
assert.Error(t, env.ActivatePlugin("foo"))
diff --git a/web/web.go b/web/web.go
index c883c750d..f74c73cde 100644
--- a/web/web.go
+++ b/web/web.go
@@ -26,12 +26,17 @@ func InitWeb() {
if *utils.Cfg.ServiceSettings.WebserverMode != "disabled" {
staticDir, _ := utils.FindDir(model.CLIENT_DIR)
l4g.Debug("Using client directory at %v", staticDir)
+
+ staticHandler := staticHandler(http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir))))
+ pluginHandler := pluginHandler(http.StripPrefix("/static/plugins/", http.FileServer(http.Dir(staticDir+"plugins/"))))
+
if *utils.Cfg.ServiceSettings.WebserverMode == "gzip" {
- mainrouter.PathPrefix("/static/").Handler(gziphandler.GzipHandler(staticHandler(http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir))))))
- } else {
- mainrouter.PathPrefix("/static/").Handler(staticHandler(http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir)))))
+ staticHandler = gziphandler.GzipHandler(staticHandler)
+ pluginHandler = gziphandler.GzipHandler(pluginHandler)
}
+ mainrouter.PathPrefix("/static/plugins/").Handler(pluginHandler)
+ mainrouter.PathPrefix("/static/").Handler(staticHandler)
mainrouter.Handle("/{anything:.*}", api.AppHandlerIndependent(root)).Methods("GET")
}
}
@@ -47,6 +52,21 @@ func staticHandler(handler http.Handler) http.Handler {
})
}
+func pluginHandler(handler http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if *utils.Cfg.ServiceSettings.EnableDeveloper {
+ w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
+ } else {
+ w.Header().Set("Cache-Control", "max-age=31556926, public")
+ }
+ if strings.HasSuffix(r.URL.Path, "/") {
+ http.NotFound(w, r)
+ return
+ }
+ handler.ServeHTTP(w, r)
+ })
+}
+
//map should be of minimum required browser version.
//var browsersNotSupported string = "MSIE/11;Internet Explorer/11;Safari/9;Chrome/43;Edge/15;Firefox/52"
//var browserMinimumSupported = [6]string{"MSIE/11", "Internet Explorer/11", "Safari/9", "Chrome/43", "Edge/15", "Firefox/52"}