diff --git a/app/plugin_requests.go b/app/plugin_requests.go index 1ccc966822..2670cbae12 100644 --- a/app/plugin_requests.go +++ b/app/plugin_requests.go @@ -5,7 +5,6 @@ package app import ( "bytes" - "fmt" "io" "net/http" "path" @@ -93,7 +92,7 @@ func (ch *Channels) ServePluginPublicRequest(w http.ResponseWriter, r *http.Requ return } - // Should be in the form of /$PLUGIN_ID/public/{anything} by the time we get here + // Should be in the form of /(subpath/)?/plugins/{plugin_id}/public/* by the time we get here vars := mux.Vars(r) pluginID := vars["plugin_id"] @@ -111,8 +110,13 @@ func (ch *Channels) ServePluginPublicRequest(w http.ResponseWriter, r *http.Requ return } + subpath, err := utils.GetSubpathFromConfig(ch.cfgSvc.Config()) + if err != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } + publicFilePath := path.Clean(r.URL.Path) - prefix := fmt.Sprintf("/plugins/%s/public/", pluginID) + prefix := path.Join(subpath, "plugins", pluginID, "public") if !strings.HasPrefix(publicFilePath, prefix) { http.NotFound(w, r) return diff --git a/app/plugin_requests_test.go b/app/plugin_requests_test.go index c41c70be6d..d5796a975f 100644 --- a/app/plugin_requests_test.go +++ b/app/plugin_requests_test.go @@ -4,23 +4,65 @@ package app import ( + "fmt" + "io" "net/http" "net/http/httptest" + "os" + "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/mattermost/mattermost-server/v6/model" + "github.com/mattermost/mattermost-server/v6/utils/fileutils" ) func TestServePluginPublicRequest(t *testing.T) { + installPlugin := func(t *testing.T, th *TestHelper, pluginID string) { + t.Helper() + + path, _ := fileutils.FindDir("tests") + fileReader, err := os.Open(filepath.Join(path, fmt.Sprintf("%s.tar.gz", pluginID))) + require.NoError(t, err) + defer fileReader.Close() + + _, appErr := th.App.WriteFile(fileReader, getBundleStorePath(pluginID)) + checkNoError(t, appErr) + + appErr = th.App.SyncPlugins() + checkNoError(t, appErr) + + env := th.App.GetPluginsEnvironment() + require.NotNil(t, env) + + // Check if installed + pluginStatus, err := env.Statuses() + require.NoError(t, err) + found := false + for _, pluginStatus := range pluginStatus { + if pluginStatus.PluginId == pluginID { + found = true + } + } + require.True(t, found, "failed to find plugin %s in plugin statuses", pluginID) + + appErr = th.App.EnablePlugin(pluginID) + checkNoError(t, appErr) + + t.Cleanup(func() { + appErr = th.App.ch.RemovePlugin(pluginID) + checkNoError(t, appErr) + }) + } + t.Run("returns not found when plugins environment is nil", func(t *testing.T) { th := Setup(t) - defer th.TearDown() + t.Cleanup(th.TearDown) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true }) - req, err := http.NewRequest("GET", "/plugins", nil) + req, err := http.NewRequest("GET", "/plugins/plugin_id/public/file.txt", nil) require.NoError(t, err) rr := httptest.NewRecorder() @@ -29,4 +71,83 @@ func TestServePluginPublicRequest(t *testing.T) { assert.Equal(t, http.StatusNotFound, rr.Code) }) + + t.Run("resolves path for valid plugin", func(t *testing.T) { + th := Setup(t) + t.Cleanup(th.TearDown) + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true }) + + path, _ := fileutils.FindDir("tests") + fileReader, err := os.Open(filepath.Join(path, "testplugin.tar.gz")) + require.NoError(t, err) + defer fileReader.Close() + + installPlugin(t, th, "testplugin") + + req, err := http.NewRequest("GET", "/plugins/testplugin/public/file.txt", nil) + require.NoError(t, err) + + rr := httptest.NewRecorder() + th.App.ch.srv.Router.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + body, err := io.ReadAll(rr.Body) + require.NoError(t, err) + require.Equal(t, "Hello World!", string(body)) + }) + + t.Run("resolves path for valid plugin when subpath configured", func(t *testing.T) { + os.Setenv("MM_SERVICESETTINGS_SITEURL", "http://localhost:8065/subpath") + defer os.Unsetenv("MM_SERVICESETTINGS_SITEURL") + + th := Setup(t) + t.Cleanup(th.TearDown) + + installPlugin(t, th, "testplugin") + + req, err := http.NewRequest("GET", "/subpath/plugins/testplugin/public/file.txt", nil) + require.NoError(t, err) + + rr := httptest.NewRecorder() + th.App.ch.srv.RootRouter.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + body, err := io.ReadAll(rr.Body) + require.NoError(t, err) + assert.Equal(t, "Hello World!", string(body)) + }) + + t.Run("fails for invalid plugin", func(t *testing.T) { + th := Setup(t) + t.Cleanup(th.TearDown) + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true }) + + req, err := http.NewRequest("GET", "/plugins/invalidplugin/public/file.txt", nil) + require.NoError(t, err) + + rr := httptest.NewRecorder() + th.App.ch.srv.Router.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusNotFound, rr.Code) + }) + + t.Run("fails attempting to break out of path", func(t *testing.T) { + os.Setenv("MM_SERVICESETTINGS_SITEURL", "http://localhost:8065/subpath") + defer os.Unsetenv("MM_SERVICESETTINGS_SITEURL") + + th := Setup(t) + t.Cleanup(th.TearDown) + + installPlugin(t, th, "testplugin") + installPlugin(t, th, "testplugin2") + + req, err := http.NewRequest("GET", "/subpath/plugins/testplugin/public/../../testplugin2/file.txt", nil) + require.NoError(t, err) + + rr := httptest.NewRecorder() + th.App.ch.srv.RootRouter.ServeHTTP(rr, req) + + require.Equal(t, http.StatusMovedPermanently, rr.Code) + assert.Equal(t, "/subpath/plugins/testplugin2/file.txt", rr.Header()["Location"][0]) + }) } diff --git a/tests/README.md b/tests/README.md index 3a464bf033..d259132da5 100644 --- a/tests/README.md +++ b/tests/README.md @@ -22,18 +22,19 @@ It is possible to manually test specific sections of any test, instead of using There are two test plugins: `testplugin.tar.gz` and `testplugin2.tar.gz`. These are use in some integration tests in the `api4` package. Any changes to the plugin bundles require updating the corresponding signatures. First, import the public and private development key: -``` -$ gpg --import ./development-public-key.gpg -$ gpg --import ./development-private-key.asc +```sh +gpg --import ./development-public-key.gpg +gpg --import ./development-private-key.asc ``` This has to be done only once. Then update the signatures: +```sh +gpg -u F3FACE45E0DE642C8BD6A8E64C7C6562C192CC1F --verbose --personal-digest-preferences SHA256 --detach-sign testplugin.tar.gz +gpg -u F3FACE45E0DE642C8BD6A8E64C7C6562C192CC1F --verbose --personal-digest-preferences SHA256 --detach-sign --armor testplugin.tar.gz +gpg -u F3FACE45E0DE642C8BD6A8E64C7C6562C192CC1F --verbose --personal-digest-preferences SHA256 --detach-sign testplugin2.tar.gz +gpg -u F3FACE45E0DE642C8BD6A8E64C7C6562C192CC1F --verbose --personal-digest-preferences SHA256 --detach-sign --armor testplugin2.tar.gz ``` -$ gpg -u F3FACE45E0DE642C8BD6A8E64C7C6562C192CC1F --verbose --personal-digest-preferences SHA256 --detach-sign testplugin.tar.gz -$ gpg -u F3FACE45E0DE642C8BD6A8E64C7C6562C192CC1F --verbose --personal-digest-preferences SHA256 --detach-sign --armor testplugin.tar.gz -$ gpg -u F3FACE45E0DE642C8BD6A8E64C7C6562C192CC1F --verbose --personal-digest-preferences SHA256 --detach-sign testplugin2.tar.gz -$ gpg -u F3FACE45E0DE642C8BD6A8E64C7C6562C192CC1F --verbose --personal-digest-preferences SHA256 --detach-sign --armor testplugin2.tar.gz Finally, include the updates bundles and signatures in your commit. diff --git a/tests/testplugin.tar.gz b/tests/testplugin.tar.gz index da716bbee3..456276f917 100644 Binary files a/tests/testplugin.tar.gz and b/tests/testplugin.tar.gz differ diff --git a/tests/testplugin.tar.gz.asc b/tests/testplugin.tar.gz.asc index 2ea3c9f3b1..7a7885f779 100644 --- a/tests/testplugin.tar.gz.asc +++ b/tests/testplugin.tar.gz.asc @@ -1,14 +1,14 @@ -----BEGIN PGP SIGNATURE----- -iQGzBAABCAAdFiEE8/rOReDeZCyL1qjmTHxlYsGSzB8FAl4sogwACgkQTHxlYsGS -zB+CiwwAqNhwq6PQeKCQyJ4F1kZBpSkHrlbaT+V89tcj5BomhxFin30XukW2tiov -+U4cfeKI+NAu9uPUxN6f4r6khQOGQK0bvun3YDemhbVozaPneNoxs+ugkBLMrwvp -v3Vbi241bTWsi6NxlwJDSM+LEYWkFXZKCjQFjX2UWEM86uocKZnjHHqqke4ZkXWm -Sal1mOvfZtx/R0+8aKt7FEbdUy4s15gRcVfnp017PD9VDwfiXSMVrdaYr3HqD2Q/ -WMMmZ9lW4Y0I6qtv+1Ud9YZAXPr8OzsgU13FXU1GcUG+L/W8jSb9XY/4EIFpb4O6 -tQGRBBtjq0EofVq8S9V6/LMPH3/CPgHufK7TWl12mnyGUOac4YmFlGtStkovIHZJ -+nxcMsV5xU3UhdM+/uBJnC5EH8sH2hQpkJugZIFruswfHNSiNKUpiHjupepfsV7v -jzKCEgh7Rv99QBSSBtZSuBitnzEWAE3X9UsEYx5qCQJvBBVLiugFHFv6MtkePRNd -ElLLqMat -=KHZP +iQGzBAABCAAdFiEE8/rOReDeZCyL1qjmTHxlYsGSzB8FAmOaRXoACgkQTHxlYsGS +zB8H1Av+MuNxBuQFxNvORGcudExCAAgQZb3ykYNVxPT1CzwVdd16B+VvRyt3+PKz +nSsyYyrdvd2xpdaEXFHBA8RxS5ZWCz/hkrdxUhBUWV8O5OUMOHDYvetWc6/9GeuR +3dd4VElpLsEs6hIpwnejR1EouNr5OhxstpnB2AOz5N7LWlG5lTKhaHs1zN1uLc4f +GmdJZ+5+PYm1UUipFj4kolkI+44Ytl6mj+tTyC4VJAj0mwnXQtp/JdFcDmmeRrTI +AwmanJKQlK3yw331FYSd/CXuqCGOh157X7Z5P2Mtr3ZOaNj7qLY0mjweqxjj4fnN +YTUu22KhRLGzggEbTg+5huYhtvqa1b87EcH6ukxWoBYQpFK+TyyhuX3ZeT5x6lFi +8SP9o/9KcQxB5oD6X5FGMR4v5VDosNnNuqW8G7g4fkcjQKY65tnX75G5Ih156BAP +dfZ6+nKCQvXgv1XRymF3UrJeddXtOMQzh4aSvYwwy46qNMPYMeDq6PoMXGVNeylP +bTrjLEtE +=OvPw -----END PGP SIGNATURE----- diff --git a/tests/testplugin.tar.gz.sig b/tests/testplugin.tar.gz.sig index 39efc817b7..280f77dc12 100644 Binary files a/tests/testplugin.tar.gz.sig and b/tests/testplugin.tar.gz.sig differ