From f9d9dd8b603a462814e204ce333e0118e2476851 Mon Sep 17 00:00:00 2001 From: threepwood-mm <54032291+threepwood-mm@users.noreply.github.com> Date: Fri, 6 Sep 2019 12:52:14 +0200 Subject: [PATCH] [MM-14521] New endpoint to validate site urls (#11944) --- api4/system.go | 27 +++++++++++++++++++++++++++ api4/system_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ app/admin.go | 11 +++++++++++ i18n/en.json | 4 ++++ model/client4.go | 16 ++++++++++++++++ 5 files changed, 100 insertions(+) diff --git a/api4/system.go b/api4/system.go index fba02a2e6e..aa587b352c 100644 --- a/api4/system.go +++ b/api4/system.go @@ -27,6 +27,7 @@ func (api *API) InitSystem() { api.BaseRoutes.ApiRoot.Handle("/audits", api.ApiSessionRequired(getAudits)).Methods("GET") api.BaseRoutes.ApiRoot.Handle("/email/test", api.ApiSessionRequired(testEmail)).Methods("POST") + api.BaseRoutes.ApiRoot.Handle("/site_url/test", api.ApiSessionRequired(testSiteURL)).Methods("POST") api.BaseRoutes.ApiRoot.Handle("/file/s3_test", api.ApiSessionRequired(testS3)).Methods("POST") api.BaseRoutes.ApiRoot.Handle("/database/recycle", api.ApiSessionRequired(databaseRecycle)).Methods("POST") api.BaseRoutes.ApiRoot.Handle("/caches/invalidate", api.ApiSessionRequired(invalidateCaches)).Methods("POST") @@ -145,6 +146,32 @@ func testEmail(c *Context, w http.ResponseWriter, r *http.Request) { ReturnStatusOK(w) } +func testSiteURL(c *Context, w http.ResponseWriter, r *http.Request) { + if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + if *c.App.Config().ExperimentalSettings.RestrictSystemAdmin { + c.Err = model.NewAppError("testSiteURL", "api.restricted_system_admin", nil, "", http.StatusForbidden) + return + } + + props := model.MapFromJson(r.Body) + siteURL := props["site_url"] + if siteURL == "" { + c.SetInvalidParam("site_url") + return + } + err := c.App.TestSiteURL(siteURL) + if err != nil { + c.Err = err + return + } + + ReturnStatusOK(w) +} + func getAudits(c *Context, w http.ResponseWriter, r *http.Request) { if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) { c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) diff --git a/api4/system_test.go b/api4/system_test.go index 824cf0f86f..6e58b56413 100644 --- a/api4/system_test.go +++ b/api4/system_test.go @@ -5,6 +5,7 @@ import ( "net/http" "net/http/httptest" "os" + "strings" "testing" "github.com/mattermost/mattermost-server/mlog" @@ -149,6 +150,47 @@ func TestEmailTest(t *testing.T) { }) } +func TestSiteURLTest(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + Client := th.Client + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasSuffix(r.URL.Path, "/valid/api/v4/system/ping") { + w.WriteHeader(200) + } else { + w.WriteHeader(400) + } + })) + defer ts.Close() + + validSiteURL := ts.URL + "/valid" + invalidSiteURL := ts.URL + "/invalid" + + t.Run("as system admin", func(t *testing.T) { + _, resp := th.SystemAdminClient.TestSiteURL("") + CheckBadRequestStatus(t, resp) + + _, resp = th.SystemAdminClient.TestSiteURL(invalidSiteURL) + CheckBadRequestStatus(t, resp) + + _, resp = th.SystemAdminClient.TestSiteURL(validSiteURL) + CheckOKStatus(t, resp) + }) + + t.Run("as system user", func(t *testing.T) { + _, resp := Client.TestSiteURL(validSiteURL) + CheckForbiddenStatus(t, resp) + }) + + t.Run("as restricted system admin", func(t *testing.T) { + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true }) + + _, resp := Client.TestSiteURL(validSiteURL) + CheckForbiddenStatus(t, resp) + }) +} + func TestDatabaseRecycle(t *testing.T) { th := Setup().InitBasic() defer th.TearDown() diff --git a/app/admin.go b/app/admin.go index f883a550dd..91898efe92 100644 --- a/app/admin.go +++ b/app/admin.go @@ -4,6 +4,7 @@ package app import ( + "fmt" "io" "os" "time" @@ -174,6 +175,16 @@ func (a *App) RecycleDatabaseConnection() { mlog.Warn("Finished recycling the database connection.") } +func (a *App) TestSiteURL(siteURL string) *model.AppError { + url := fmt.Sprintf("%s/api/v4/system/ping", siteURL) + res, err := http.Get(url) + if err != nil || res.StatusCode != 200 { + return model.NewAppError("testSiteURL", "app.admin.test_site_url.failure", nil, "", http.StatusBadRequest) + } + + return nil +} + func (a *App) TestEmail(userId string, cfg *model.Config) *model.AppError { if len(*cfg.EmailSettings.SMTPServer) == 0 { return model.NewAppError("testEmail", "api.admin.test_email.missing_server", nil, utils.T("api.context.invalid_param.app_error", map[string]interface{}{"Name": "SMTPServer"}), http.StatusBadRequest) diff --git a/i18n/en.json b/i18n/en.json index 71256f8f08..d2e46f64f3 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -2774,6 +2774,10 @@ "id": "app.admin.test_email.failure", "translation": "Connection unsuccessful: {{.Error}}" }, + { + "id": "app.admin.test_site_url.failure", + "translation": "This is not a valid live URL" + }, { "id": "app.channel.create_channel.no_team_id.app_error", "translation": "Must specify the team ID to create a channel" diff --git a/model/client4.go b/model/client4.go index 2cd2a69741..a81696597c 100644 --- a/model/client4.go +++ b/model/client4.go @@ -268,6 +268,10 @@ func (c *Client4) GetTestEmailRoute() string { return fmt.Sprintf("/email/test") } +func (c *Client4) GetTestSiteURLRoute() string { + return fmt.Sprintf("/site_url/test") +} + func (c *Client4) GetTestS3Route() string { return fmt.Sprintf("/file/s3_test") } @@ -2912,6 +2916,18 @@ func (c *Client4) TestEmail(config *Config) (bool, *Response) { return CheckStatusOK(r), BuildResponse(r) } +// TestSiteURL will test the validity of a site URL. +func (c *Client4) TestSiteURL(siteURL string) (bool, *Response) { + requestBody := make(map[string]string) + requestBody["site_url"] = siteURL + r, err := c.DoApiPost(c.GetTestSiteURLRoute(), MapToJson(requestBody)) + if err != nil { + return false, BuildErrorResponse(r, err) + } + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) +} + // TestS3Connection will attempt to connect to the AWS S3. func (c *Client4) TestS3Connection(config *Config) (bool, *Response) { r, err := c.DoApiPost(c.GetTestS3Route(), config.ToJson())